From: Michael Hennerich <michael.hennerich@xxxxxxxxxx> Suitable FPGA interface HDL is available here: http://wiki.analog.com/resources/fpga/xilinx/fmc/ad9467 Signed-off-by: Michael Hennerich <michael.hennerich@xxxxxxxxxx> --- drivers/staging/iio/adc/Kconfig | 13 + drivers/staging/iio/adc/Makefile | 3 + drivers/staging/iio/adc/cf_ad9467.h | 161 ++++++++ drivers/staging/iio/adc/cf_ad9467_core.c | 585 ++++++++++++++++++++++++++++++ drivers/staging/iio/adc/cf_ad9467_ring.c | 214 +++++++++++ 5 files changed, 976 insertions(+), 0 deletions(-) create mode 100644 drivers/staging/iio/adc/cf_ad9467.h create mode 100644 drivers/staging/iio/adc/cf_ad9467_core.c create mode 100644 drivers/staging/iio/adc/cf_ad9467_ring.c diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig index d9decea..266e807 100644 --- a/drivers/staging/iio/adc/Kconfig +++ b/drivers/staging/iio/adc/Kconfig @@ -169,6 +169,19 @@ config AD7280 To compile this driver as a module, choose M here: the module will be called ad7280a +config CF_AD9467 + tristate "Analog Devices AD9467 AD9643 High-Speed ADC driver" + select IIO_BUFFER + help + Say yes here to build support for Analog Devices AD9467 and AD9643, + High-Speed LVDS analog to digital converters (ADC). + FPGA interface HDL is available here: + http://wiki.analog.com/resources/fpga/xilinx/fmc/ad9467 + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called cf_ad9467. + config MAX1363 tristate "Maxim max1363 ADC driver" depends on I2C diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile index ceee7f3..2f37d85 100644 --- a/drivers/staging/iio/adc/Makefile +++ b/drivers/staging/iio/adc/Makefile @@ -29,6 +29,9 @@ ad7298-y := ad7298_core.o ad7298-$(CONFIG_IIO_BUFFER) += ad7298_ring.o obj-$(CONFIG_AD7298) += ad7298.o +cf_ad9467-y := cf_ad9467_core.o cf_ad9467_ring.o +obj-$(CONFIG_CF_AD9467) += cf_ad9467.o + obj-$(CONFIG_AD7291) += ad7291.o obj-$(CONFIG_AD7780) += ad7780.o obj-$(CONFIG_AD7793) += ad7793.o diff --git a/drivers/staging/iio/adc/cf_ad9467.h b/drivers/staging/iio/adc/cf_ad9467.h new file mode 100644 index 0000000..1423e4c --- /dev/null +++ b/drivers/staging/iio/adc/cf_ad9467.h @@ -0,0 +1,161 @@ +/* + * ADI-AIM ADI ADC Interface Module + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + * http://wiki.analog.com/resources/fpga/xilinx/fmc/ad9467 + */ + +#ifndef ADI_AIM_H_ +#define ADI_AIM_H_ + +/* PCORE CoreFPGA register map */ + +#define AD9467_PCORE_VERSION 0x00 +#define AD9467_PCORE_SPI_CTRL 0x04 +#define AD9467_PCORE_SPI_RDSTAT 0x08 +#define AD9467_PCORE_DMA_CTRL 0x0C +#define AD9467_PCORE_DMA_STAT 0x10 +#define AD9467_PCORE_ADC_STAT 0x14 +#define AD9467_PCORE_PN_ERR_CTRL 0x24 + +/* AD9467_PCORE_SPI_CTRL */ +#define AD9647_SPI_START (1 << 25) +#define AD9647_SPI_SEL(x) (((x) & 0x1) << 24) +#define AD9647_SPI_READ (1 << 23) +#define AD9647_SPI_WRITE (0 << 23) +#define AD9647_SPI_ADDR(x) (((x) & 0x1FFF) << 8) +#define AD9647_SPI_DATA(x) (((x) & 0xFF) << 0) + +/* AD9467_PCORE_SPI_RDSTAT */ +#define AD9647_SPI_IDLE (1 << 8) +#define AD9647_SPI_READVAL(x) ((x) & 0xFF) + +/* AD9467_PCORE_DMA_CTRL */ +#define AD9647_DMA_CAP_EN (1 << 16) +#define AD9647_DMA_CNT(x) (((x) & 0xFFFF) << 0) + +/* AD9467_PCORE_DMA_STAT */ +#define AD9647_DMA_STAT_BUSY (1 << 0) +#define AD9647_DMA_STAT_OVF (1 << 1) +#define AD9647_DMA_STAT_UNF (1 << 2) + +/* AD9467_PCORE_ADC_STAT */ +#define AD9467_PCORE_ADC_STAT_OVR (1 << 0) +#define AD9467_PCORE_ADC_STAT_PN_OOS (1 << 1) /* W1C */ +#define AD9467_PCORE_ADC_STAT_PN_ERR (1 << 2) /* W1C */ + +/* AD9467_PCORE_PN_ERR_CTRL */ +#define AD9467_PN23_EN (1 << 0) +#define AD9467_PN9_EN (0 << 0) + +/* + * ADI High-Speed ADC common spi interface registers + * See Application-Note AN-877 + */ + +#define ADC_REG_CHIP_PORT_CONF 0x00 +#define ADC_REG_CHIP_ID 0x01 +#define ADC_REG_CHIP_GRADE 0x02 +#define ADC_REG_TRANSFER 0xFF +#define ADC_REG_MODES 0x08 +#define ADC_REG_TEST_IO 0x0D +#define ADC_REG_ADC_INPUT 0x0F +#define ADC_REG_OFFSET 0x10 +#define ADC_REG_OUTPUT_MODE 0x14 +#define ADC_REG_OUTPUT_ADJUST 0x15 +#define ADC_REG_OUTPUT_PHASE 0x16 +#define ADC_REG_OUTPUT_DELAY 0x17 +#define ADC_REG_VREF 0x18 +#define ADC_REG_ANALOG_INPUT 0x2C + +/* ADC_REG_TRANSFER */ +#define TRANSFER_SYNC 0x1 + +/* ADC_REG_TEST_IO */ +#define TESTMODE_OFF 0x0 +#define TESTMODE_MIDSCALE_SHORT 0x1 +#define TESTMODE_POS_FULLSCALE 0x2 +#define TESTMODE_NEG_FULLSCALE 0x3 +#define TESTMODE_ALT_CHECKERBOARD 0x4 +#define TESTMODE_PN23_SEQ 0x5 +#define TESTMODE_PN9_SEQ 0x6 +#define TESTMODE_ONE_ZERO_TOGGLE 0x7 + +/* ADC_REG_OUTPUT_MODE */ +#define OUTPUT_MODE_OFFSET_BINARY 0x0 +#define OUTPUT_MODE_TWOS_COMPLEMENT 0x1 +#define OUTPUT_MODE_GRAY_CODE 0x2 + +/* + * Analog Devices AD9467 16-Bit, 200/250 MSPS ADC + */ + +#define AD9467_DEF_OUTPUT_MODE 0x08 +#define AD9467_REG_VREF_MASK 0x0F +#define CHIPID_AD9467 0x50 + +/* + * Analog Devices AD9643 Dual 14-Bit, 170/210/250 MSPS ADC + */ + +#define CHIPID_AD9643 0x82 +#define AD9643_REG_VREF_MASK 0x1F +#define AD9643_DEF_OUTPUT_MODE 0x04 + +enum { + ID_AD9467, + ID_AD9643, +}; + +struct aim_chip_info { + char name[8]; + unsigned num_channels; + unsigned long available_scan_masks[2]; + const int (*scale_table)[2]; + int num_scales; + struct iio_chan_spec channel[2]; +}; + +struct aim_state { + struct spi_device *spi; + struct mutex lock; + struct completion dma_complete; + struct dma_chan *rx_chan; + const struct aim_chip_info *chip_info; + void __iomem *regs; + void *buf_virt; + dma_addr_t buf_phys; + unsigned spi_ssel; + unsigned ring_lenght; + unsigned bytes_per_datum; + unsigned id; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + + unsigned char data[3] ____cacheline_aligned; +}; + +/* + * IO accessors + */ + +static inline void aim_write(struct aim_state *st, unsigned reg, unsigned val) +{ + iowrite32(val, st->regs + reg); +} + +static inline unsigned int aim_read(struct aim_state *st, unsigned reg) +{ + return ioread32(st->regs + reg); +} + +void aim_register_ring_funcs(struct iio_dev *indio_dev); +int aim_configure_ring(struct iio_dev *indio_dev); +void aim_unconfigure_ring(struct iio_dev *indio_dev); + +#endif /* ADI_AIM_H_ */ diff --git a/drivers/staging/iio/adc/cf_ad9467_core.c b/drivers/staging/iio/adc/cf_ad9467_core.c new file mode 100644 index 0000000..811a659 --- /dev/null +++ b/drivers/staging/iio/adc/cf_ad9467_core.c @@ -0,0 +1,585 @@ +/* + * ADI-AIM ADI ADC Interface Module + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + * http://wiki.analog.com/resources/fpga/xilinx/fmc/ad9467 + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/wait.h> +#include <linux/spi/spi.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/delay.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> + +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_spi.h> + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "cf_ad9467.h" + +static int aim_spi_read(struct aim_state *st, unsigned reg) +{ + unsigned long timeout = jiffies + HZ / 4; + int ret; + + if (st->spi) { + unsigned char *buf = st->data; + buf[0] = 0x80 | (reg >> 8); + buf[1] = reg & 0xFF; + + ret = spi_write_then_read(st->spi, &buf[0], 2, &buf[2], 1); + if (ret < 0) + return ret; + + return buf[2]; + } + + aim_write(st, AD9467_PCORE_SPI_CTRL, 0); + aim_write(st, AD9467_PCORE_SPI_CTRL, + AD9647_SPI_START | + AD9647_SPI_READ | + AD9647_SPI_SEL(st->spi_ssel) | + AD9647_SPI_ADDR(reg)); + + while (!(aim_read(st, AD9467_PCORE_SPI_RDSTAT) & AD9647_SPI_IDLE)) { + cpu_relax(); + if (time_after(jiffies, timeout)) + return -EIO; + } + + return AD9647_SPI_READVAL(aim_read(st, AD9467_PCORE_SPI_RDSTAT)); +} + +static int aim_spi_write(struct aim_state *st, unsigned reg, unsigned val) +{ + unsigned long timeout = jiffies + HZ / 4; + + int ret; + + if (st->spi) { + unsigned char *buf = st->data; + buf[0] = reg >> 8; + buf[1] = reg & 0xFF; + buf[2] = val; + ret = spi_write(st->spi, buf, 3); + if (ret < 0) + return ret; + + return 0; + } + + aim_write(st, AD9467_PCORE_SPI_CTRL, 0); + aim_write(st, AD9467_PCORE_SPI_CTRL, + AD9647_SPI_START | + AD9647_SPI_WRITE | + AD9647_SPI_SEL(st->spi_ssel) | + AD9647_SPI_ADDR(reg) | + AD9647_SPI_DATA(val)); + + + while (!(aim_read(st, AD9467_PCORE_SPI_RDSTAT) & AD9647_SPI_IDLE)) { + cpu_relax(); + if (time_after(jiffies, timeout)) + return -EIO; + } + + return 0; +} + +static int aim_debugfs_open(struct inode *inode, struct file *file) +{ + if (inode->i_private) + file->private_data = inode->i_private; + + return 0; +} + +static ssize_t aim_debugfs_pncheck_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct iio_dev *indio_dev = file->private_data; + struct aim_state *st = iio_priv(indio_dev); + char buf[80], *p = buf; + unsigned stat; + + stat = aim_read(st, AD9467_PCORE_VERSION); + p += sprintf(p, "%s %s\n", stat & AD9467_PCORE_ADC_STAT_PN_OOS ? + "Out of Sync" : "", stat & AD9467_PCORE_ADC_STAT_PN_ERR ? + "PN Error" : ""); + + return simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); +} + +static ssize_t aim_debugfs_pncheck_write(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + struct iio_dev *indio_dev = file->private_data; + struct aim_state *st = iio_priv(indio_dev); + unsigned mode; + char buf[80], *p = buf; + + count = min_t(size_t, count, (sizeof(buf)-1)); + if (copy_from_user(p, userbuf, count)) + return -EFAULT; + + if (sysfs_streq(p, "PN9")) + mode = TESTMODE_PN9_SEQ; + else if (sysfs_streq(p, "PN23")) + mode = TESTMODE_PN23_SEQ; + else + mode = TESTMODE_OFF; + + aim_spi_write(st, ADC_REG_TEST_IO, mode); + aim_spi_write(st, ADC_REG_TRANSFER, TRANSFER_SYNC); + + aim_write(st, AD9467_PCORE_PN_ERR_CTRL, mode == TESTMODE_PN23_SEQ ? + AD9467_PN23_EN : AD9467_PN9_EN); + + mdelay(1); + + aim_write(st, AD9467_PCORE_ADC_STAT, + AD9467_PCORE_ADC_STAT_PN_OOS | + AD9467_PCORE_ADC_STAT_PN_OOS | + AD9467_PCORE_ADC_STAT_OVR); + + return count; +} + +static const struct file_operations aim_debugfs_pncheck_fops = { + .open = aim_debugfs_open, + .read = aim_debugfs_pncheck_read, + .write = aim_debugfs_pncheck_write, +}; + +static int aim_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct aim_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&indio_dev->mlock); + if (readval == NULL) { + ret = aim_spi_write(st, reg, writeval); + aim_spi_write(st, ADC_REG_TRANSFER, TRANSFER_SYNC); + } else { + ret = aim_spi_read(st, reg); + if (ret < 0) + return ret; + *readval = ret; + ret = 0; + } + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int aim_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct aim_state *st = iio_priv(indio_dev); + int i; + unsigned vref_val; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + vref_val = aim_spi_read(st, ADC_REG_VREF) & + (st->id == CHIPID_AD9643 ? AD9643_REG_VREF_MASK : + AD9467_REG_VREF_MASK); + + for (i = 0; i < st->chip_info->num_scales; i++) + if (vref_val == st->chip_info->scale_table[i][1]) + break; + + *val = 0; + *val2 = st->chip_info->scale_table[i][0]; + + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + + +static int aim_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct aim_state *st = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + + for (i = 0; i < st->chip_info->num_scales; i++) + if (val2 == st->chip_info->scale_table[i][0]) + break; + + aim_spi_write(st, ADC_REG_VREF, + st->chip_info->scale_table[i][1]); + aim_spi_write(st, ADC_REG_TRANSFER, TRANSFER_SYNC); + + return 0; + default: + return -EINVAL; + } +} + +static ssize_t aim_show_scale_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct aim_state *st = iio_priv(indio_dev); + int i, len = 0; + + for (i = 0; i < st->chip_info->num_scales; i++) + len += sprintf(buf + len, "0.%06u ", + st->chip_info->scale_table[i][0]); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO, + aim_show_scale_available, NULL, 0); + +static struct attribute *aim_attributes[] = { + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group aim_attribute_group = { + .attrs = aim_attributes, +}; + +static const int ad9467_scale_table[][2] = { + {30517, 0}, {32043, 6}, {33569, 7}, + {35095, 8}, {36621, 9}, {38146, 10}, +}; + +static const int ad9643_scale_table[][2] = { + {31738, 0xF}, {31403, 0xE}, {31067, 0xD}, {30731, 0xC}, {30396, 0xB}, + {30060, 0xA}, {29724, 0x9}, {29388, 0x8}, {29053, 0x7}, {28717, 0x6}, + {28381, 0x5}, {28046, 0x4}, {27710, 0x3}, {27374, 0x2}, {27039, 0x1}, + {26703, 0x0}, {26367, 0x1F}, {26031, 0x1E}, {25696, 0x1D}, + {25360, 0x1C}, {25024, 0x1B}, {24689, 0x1A}, {24353, 0x19}, + {24017, 0x18}, {23682, 0x17}, {23346, 0x16}, {23010, 0x15}, + {22675, 0x14}, {22339, 0x13}, {22003, 0x12}, {21667, 0x11}, + {21332, 0x10}, +}; + +#define AIM_CHAN(_chan, _name, _si, _bits, _sign) \ + { .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .extend_name = _name, \ + .channel = _chan, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .scan_index = _si, \ + .scan_type = IIO_ST(_sign, _bits, 16, 0)} + +static const struct aim_chip_info aim_chip_info_tbl[] = { + [ID_AD9467] = { + .name = "AD9467", + .scale_table = ad9467_scale_table, + .num_scales = ARRAY_SIZE(ad9467_scale_table), + .num_channels = 1, + .available_scan_masks[0] = BIT(0), + .channel[0] = AIM_CHAN(0, NULL, 0, 16, 's'), + }, + [ID_AD9643] = { + .name = "AD9643", + .scale_table = ad9643_scale_table, + .num_scales = ARRAY_SIZE(ad9643_scale_table), + .num_channels = 2, + .available_scan_masks[0] = BIT(0) | BIT(1), + .channel[0] = AIM_CHAN(0, "I", 0, 14, 'u'), + .channel[1] = AIM_CHAN(1, "Q", 1, 14, 'u'), + }, +}; + +static const struct iio_info aim_info = { + .driver_module = THIS_MODULE, + .read_raw = &aim_read_raw, + .write_raw = &aim_write_raw, + .attrs = &aim_attribute_group, + .debugfs_reg_access = aim_reg_access, +}; + +struct aim_dma_params { + struct device_node *of_node; + int chan_id; +}; + +static bool aim_dma_filter(struct dma_chan *chan, void *param) +{ + struct aim_dma_params *p = param; + + return chan->device->dev->of_node == p->of_node && + chan->chan_id == p->chan_id; +} + +/** + * aim_of_probe - probe method for the AIM device. + * @of_dev: pointer to OF device structure + * @match: pointer to the structure used for matching a device + * + * This function probes the AIM device in the device tree. + * It initializes the driver data structure and the hardware. + * It returns 0, if the driver is bound to the AIM device, or a negative + * value if there is an error. + */ +static int __devinit aim_of_probe(struct platform_device *op) +{ + struct iio_dev *indio_dev; + struct device *dev = &op->dev; + struct aim_state *st; + struct resource r_mem; /* IO mem resources */ + struct spi_master *spi_master; + struct device_node *nspi; + struct aim_dma_params dma_params; + struct of_phandle_args dma_spec; + dma_cap_mask_t mask; + resource_size_t remap_size, phys_addr; + unsigned def_mode; + int ret; + + dev_info(dev, "Device Tree Probing \'%s\'\n", + op->dev.of_node->name); + + /* Get iospace for the device */ + ret = of_address_to_resource(op->dev.of_node, 0, &r_mem); + if (ret) { + dev_err(dev, "invalid address\n"); + return ret; + } + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + dev_set_drvdata(dev, indio_dev); + mutex_init(&st->lock); + + phys_addr = r_mem.start; + remap_size = resource_size(&r_mem); + if (!request_mem_region(phys_addr, remap_size, KBUILD_MODNAME)) { + dev_err(dev, "Couldn't lock memory region at 0x%08llX\n", + (unsigned long long)phys_addr); + ret = -EBUSY; + goto failed1; + } + + st->regs = ioremap(phys_addr, remap_size); + if (st->regs == NULL) { + dev_err(dev, "Couldn't ioremap memory at 0x%08llX\n", + (unsigned long long)phys_addr); + ret = -EFAULT; + goto failed2; + } + /* Get dma channel for the device */ + ret = of_parse_phandle_with_args(op->dev.of_node, "dma-request", + "#dma-cells", 0, &dma_spec); + if (ret) { + dev_err(dev, "Couldn't parse dma-request\n"); + goto failed2; + } + + dma_params.of_node = dma_spec.np; + dma_params.chan_id = dma_spec.args[0]; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE | DMA_PRIVATE, mask); + + st->rx_chan = dma_request_channel(mask, aim_dma_filter, &dma_params); + if (!st->rx_chan) { + dev_err(dev, "failed to find rx dma device\n"); + goto failed2; + } + /* + * Get SPI configuration interface for the device + * We are platform_driver, so we need other means to get the + * associated control interface. + */ + ret = of_property_read_u32(op->dev.of_node, + "spibus-slaveselect-connected", + &st->spi_ssel); + if (ret) { + dev_err(dev, "failed to get connected SPI slave select\n"); + goto failed3; + } + + nspi = of_parse_phandle(op->dev.of_node, "spibus-connected", 0); + spi_master = spi_of_node_to_master(nspi); + + if (spi_master != NULL) { + struct spi_board_info info = { + .modalias = KBUILD_MODNAME, + .max_speed_hz = 1000000, + .chip_select = st->spi_ssel, + .mode = SPI_MODE_0 | SPI_3WIRE, + }; + st->spi = spi_new_device(spi_master, &info); + if (st->spi == NULL) { + dev_err(dev, "failed to add spi device\n"); + goto failed3; + } + } else { + dev_dbg(dev, "could not find SPI master node," + "using pcore spi implementation\n"); + } + + /* Probe device */ + st->id = aim_spi_read(st, ADC_REG_CHIP_ID); + + switch (st->id) { + case CHIPID_AD9467: + st->chip_info = &aim_chip_info_tbl[ID_AD9467]; + def_mode = AD9467_DEF_OUTPUT_MODE | OUTPUT_MODE_TWOS_COMPLEMENT; + break; + case CHIPID_AD9643: + st->chip_info = &aim_chip_info_tbl[ID_AD9643]; + def_mode = AD9643_DEF_OUTPUT_MODE | OUTPUT_MODE_OFFSET_BINARY; + break; + default: + dev_err(dev, "Unrecognized CHIP_ID 0x%X\n", st->id); + ret = -ENODEV; + goto failed3; + } + + indio_dev->dev.parent = dev; + indio_dev->name = op->dev.of_node->name; + indio_dev->channels = st->chip_info->channel; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->available_scan_masks = st->chip_info->available_scan_masks; + indio_dev->info = &aim_info; + + init_completion(&st->dma_complete); + + aim_spi_write(st, ADC_REG_OUTPUT_MODE, def_mode); + aim_spi_write(st, ADC_REG_TEST_IO, TESTMODE_OFF); + aim_spi_write(st, ADC_REG_TRANSFER, TRANSFER_SYNC); + + aim_configure_ring(indio_dev); + aim_register_ring_funcs(indio_dev); + ret = iio_buffer_register(indio_dev, + st->chip_info->channel, + ARRAY_SIZE(st->chip_info->channel)); + if (ret) + goto failed4; + + ret = iio_device_register(indio_dev); + if (ret) + goto failed4; + + dev_info(dev, "ADI AIM (0x%X) at 0x%08llX mapped to 0x%p," + " DMA-%d probed ADC %s\n", + aim_read(st, AD9467_PCORE_VERSION), + (unsigned long long)phys_addr, st->regs, + st->rx_chan->chan_id, st->chip_info->name); + + if (indio_dev->debugfs_dentry) + debugfs_create_file("pseudorandom_err_check", 0644, + indio_dev->debugfs_dentry, + indio_dev, &aim_debugfs_pncheck_fops); + + return 0; + +failed4: + aim_unconfigure_ring(indio_dev); +failed3: + dma_release_channel(st->rx_chan); +failed2: + release_mem_region(phys_addr, remap_size); +failed1: + iio_free_device(indio_dev); + dev_set_drvdata(dev, NULL); + + return ret; +} + +/** + * aim_of_remove - unbinds the driver from the AIM device. + * @of_dev: pointer to OF device structure + * + * This function is called if a device is physically removed from the system or + * if the driver module is being unloaded. It frees any resources allocated to + * the device. + */ +static int __devexit aim_of_remove(struct platform_device *op) +{ + struct device *dev = &op->dev; + struct resource r_mem; /* IO mem resources */ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct aim_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); + aim_unconfigure_ring(indio_dev); + + dma_release_channel(st->rx_chan); + + iounmap(st->regs); + + /* Get iospace of the device */ + if (of_address_to_resource(op->dev.of_node, 0, &r_mem)) + dev_err(dev, "invalid address\n"); + else + release_mem_region(r_mem.start, resource_size(&r_mem)); + + iio_free_device(indio_dev); + + dev_set_drvdata(dev, NULL); + + return 0; +} + +/* Match table for of_platform binding */ +static const struct of_device_id aim_of_match[] __devinitconst = { + { .compatible = "xlnx,cf-ad9467-core-1.00.a", }, + { .compatible = "xlnx,cf-ad9643-core-1.00.a", }, + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(of, aim_of_match); + +static struct platform_driver aim_of_driver = { + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .of_match_table = aim_of_match, + }, + .probe = aim_of_probe, + .remove = __devexit_p(aim_of_remove), +}; + +module_platform_driver(aim_of_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@xxxxxxxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Analog Devices ADI-AIM"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/cf_ad9467_ring.c b/drivers/staging/iio/adc/cf_ad9467_ring.c new file mode 100644 index 0000000..c6b2230 --- /dev/null +++ b/drivers/staging/iio/adc/cf_ad9467_ring.c @@ -0,0 +1,214 @@ +/* + * ADI-AIM ADC Interface Module + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/poll.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" +#include "../ring_hw.h" +#include "cf_ad9467.h" + +static int aim_read_first_n_hw_rb(struct iio_buffer *r, + size_t count, char __user *buf) +{ + struct iio_hw_buffer *hw_ring = iio_to_hw_buf(r); + struct iio_dev *indio_dev = hw_ring->private; + struct aim_state *st = iio_priv(indio_dev); + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + unsigned rcount; + int ret; + + mutex_lock(&st->lock); + if (count == 0) { + ret = -EINVAL; + goto error_ret; + } + + if (count % 8) + rcount = (count + 8) & 0xFFFFFFF8; + else + rcount = count; + + st->buf_virt = dma_alloc_coherent(indio_dev->dev.parent, + PAGE_ALIGN(rcount), &st->buf_phys, + GFP_KERNEL); + if (st->buf_virt == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + desc = dmaengine_prep_slave_single(st->rx_chan, st->buf_phys, rcount, + DMA_FROM_DEVICE, DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(indio_dev->dev.parent, + "Failed to allocate a dma descriptor\n"); + ret = -ENOMEM; + goto error_free; + } + + desc->callback = (dma_async_tx_callback) complete; + desc->callback_param = &st->dma_complete; + + cookie = dmaengine_submit(desc); + if (cookie < 0) { + dev_err(indio_dev->dev.parent, + "Failed to submit a dma transfer\n"); + ret = cookie; + goto error_free; + } + + dma_async_issue_pending(st->rx_chan); + + aim_write(st, AD9467_PCORE_DMA_CTRL, 0); + aim_read(st, AD9467_PCORE_DMA_STAT); + aim_write(st, AD9467_PCORE_DMA_CTRL, + AD9647_DMA_CAP_EN | AD9647_DMA_CNT((rcount / 8) - 1)); + + ret = wait_for_completion_interruptible_timeout(&st->dma_complete, + 2 * HZ); + if (ret == 0) { + ret = -ETIMEDOUT; + goto error_free; + } else if (ret < 0) { + goto error_free; + } + + if (copy_to_user(buf, st->buf_virt, count)) + ret = -EFAULT; + +error_free: + dma_free_coherent(indio_dev->dev.parent, PAGE_ALIGN(rcount), + st->buf_virt, st->buf_phys); + r->stufftoread = 0; +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : count; +} + +static int aim_ring_get_length(struct iio_buffer *r) +{ + struct iio_hw_buffer *hw_ring = iio_to_hw_buf(r); + struct iio_dev *indio_dev = hw_ring->private; + struct aim_state *st = iio_priv(indio_dev); + + return st->ring_lenght; +} + +static int aim_ring_set_length(struct iio_buffer *r, int length) +{ + struct iio_hw_buffer *hw_ring = iio_to_hw_buf(r); + struct iio_dev *indio_dev = hw_ring->private; + struct aim_state *st = iio_priv(indio_dev); + + st->ring_lenght = length; + + return 0; +} + +static int aim_ring_get_bytes_per_datum(struct iio_buffer *r) +{ + struct iio_hw_buffer *hw_ring = iio_to_hw_buf(r); + struct iio_dev *indio_dev = hw_ring->private; + struct aim_state *st = iio_priv(indio_dev); + + return st->bytes_per_datum; +} + +static IIO_BUFFER_ENABLE_ATTR; +static IIO_BUFFER_LENGTH_ATTR; + +static struct attribute *aim_ring_attributes[] = { + &dev_attr_length.attr, + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group aim_ring_attr = { + .attrs = aim_ring_attributes, + .name = "buffer", +}; + +static struct iio_buffer *aim_rb_allocate(struct iio_dev *indio_dev) +{ + struct iio_buffer *buf; + struct iio_hw_buffer *ring; + + ring = kzalloc(sizeof *ring, GFP_KERNEL); + if (!ring) + return NULL; + + ring->private = indio_dev; + buf = &ring->buf; + buf->stufftoread = 0; + buf->attrs = &aim_ring_attr; + iio_buffer_init(buf); + + return buf; +} + +static inline void aim_rb_free(struct iio_buffer *r) +{ + kfree(iio_to_hw_buf(r)); +} + +static const struct iio_buffer_access_funcs aim_ring_access_funcs = { + .read_first_n = &aim_read_first_n_hw_rb, + .get_length = &aim_ring_get_length, + .set_length = &aim_ring_set_length, + .get_bytes_per_datum = &aim_ring_get_bytes_per_datum, +}; + +int aim_configure_ring(struct iio_dev *indio_dev) +{ + indio_dev->buffer = aim_rb_allocate(indio_dev); + if (indio_dev->buffer == NULL) + return -ENOMEM; + + indio_dev->modes |= INDIO_BUFFER_HARDWARE; + indio_dev->buffer->access = &aim_ring_access_funcs; + + return 0; +} + +void aim_unconfigure_ring(struct iio_dev *indio_dev) +{ + aim_rb_free(indio_dev->buffer); +} + +static inline int __aim_hw_ring_state_set(struct iio_dev *indio_dev, bool state) +{ + return 0; +} + +static int aim_hw_ring_preenable(struct iio_dev *indio_dev) +{ + return __aim_hw_ring_state_set(indio_dev, 1); +} + +static int aim_hw_ring_postdisable(struct iio_dev *indio_dev) +{ + return __aim_hw_ring_state_set(indio_dev, 0); +} + +static const struct iio_buffer_setup_ops aim_ring_setup_ops = { + .preenable = &aim_hw_ring_preenable, + .postdisable = &aim_hw_ring_postdisable, +}; + +void aim_register_ring_funcs(struct iio_dev *indio_dev) +{ + indio_dev->setup_ops = &aim_ring_setup_ops; +} -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html