On Thu, 6 Jul 2017 12:39:33 -1000 Jack Andersen <jackoalan@xxxxxxxxx> wrote: > This patch adds support for Diolan DLN2 ADC via IIO's ADC interface. > ADC is the fourth and final component of the DLN2 for the kernel. > > Signed-off-by: Jack Andersen <jackoalan@xxxxxxxxx> Applied to the togreg branch of iio.git. Initially pushed out as testing for the autobuilders to play with it. Thanks for your persistence with this. Nice to have a driver covering this element of what is a very handy bit of kit! Jonathan > --- > Changes in v2: > - Address Peter's remarks; bringing consistency to C99 syntax, dev_dbg usage, > and error handling. > - Remove probe/remove logging. > - Utilize stack-allocated buffer for pushing into IIO. > - Add timestamp channel to IIO spec. > > Suggested-by: Peter Meerwald-Stadler <pmeerw@xxxxxxxxxx> > --- > Changes in v3: > - Remove lazy channel enabling; integrating with update_channel_mode instead. > - Implement memcpy demux table similar to subsystem core. > - Use iio_triggered_buffer_setup to create buffer. > > Suggested-by: Jonathan Cameron <jic23@xxxxxxxxxx> > --- > Changes in v4: > - Remove unrelated, erronous change to lsgpio.c > --- > Changes in v5: > - Use of devm functions wherever possible. > - Use iio_device_claim_direct_mode. > - Variable declaration more consistent with C style. > - MFD modifications in separate patch. > Suggested-by: Jonathan Cameron <jic23@xxxxxxxxxx> > --- > Changes in v6: > - Don't use devm_iio_device_register to ensure correct unwind order. > Suggested-by: Lee Jones <lee.jones@xxxxxxxxxx> > --- > drivers/iio/adc/Kconfig | 9 + > drivers/iio/adc/Makefile | 1 + > drivers/iio/adc/dln2-adc.c | 722 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 732 insertions(+) > create mode 100644 drivers/iio/adc/dln2-adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 614fa41..196d2ce 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -239,6 +239,15 @@ config DA9150_GPADC > To compile this driver as a module, choose M here: the module will be > called berlin2-adc. > > +config DLN2_ADC > + tristate "Diolan DLN-2 ADC driver support" > + depends on MFD_DLN2 > + help > + Say yes here to build support for Diolan DLN-2 ADC. > + > + This driver can also be built as a module. If so, the module will be > + called adc_dln2. > + > config ENVELOPE_DETECTOR > tristate "Envelope detector using a DAC and a comparator" > depends on OF > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index b546736a..7ea0bf3 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -24,6 +24,7 @@ obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o > obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o > obj-$(CONFIG_CPCAP_ADC) += cpcap-adc.o > obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o > +obj-$(CONFIG_DLN2_ADC) += dln2-adc.o > obj-$(CONFIG_ENVELOPE_DETECTOR) += envelope-detector.o > obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o > obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o > diff --git a/drivers/iio/adc/dln2-adc.c b/drivers/iio/adc/dln2-adc.c > new file mode 100644 > index 0000000..ab8d6ae > --- /dev/null > +++ b/drivers/iio/adc/dln2-adc.c > @@ -0,0 +1,722 @@ > +/* > + * Driver for the Diolan DLN-2 USB-ADC adapter > + * > + * Copyright (c) 2017 Jack Andersen > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License as > + * published by the Free Software Foundation, version 2. > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/types.h> > +#include <linux/platform_device.h> > +#include <linux/mfd/dln2.h> > + > +#include <linux/iio/iio.h> > +#include <linux/iio/sysfs.h> > +#include <linux/iio/trigger.h> > +#include <linux/iio/trigger_consumer.h> > +#include <linux/iio/triggered_buffer.h> > +#include <linux/iio/buffer.h> > +#include <linux/iio/kfifo_buf.h> > + > +#define DLN2_ADC_MOD_NAME "dln2-adc" > + > +#define DLN2_ADC_ID 0x06 > + > +#define DLN2_ADC_GET_CHANNEL_COUNT DLN2_CMD(0x01, DLN2_ADC_ID) > +#define DLN2_ADC_ENABLE DLN2_CMD(0x02, DLN2_ADC_ID) > +#define DLN2_ADC_DISABLE DLN2_CMD(0x03, DLN2_ADC_ID) > +#define DLN2_ADC_CHANNEL_ENABLE DLN2_CMD(0x05, DLN2_ADC_ID) > +#define DLN2_ADC_CHANNEL_DISABLE DLN2_CMD(0x06, DLN2_ADC_ID) > +#define DLN2_ADC_SET_RESOLUTION DLN2_CMD(0x08, DLN2_ADC_ID) > +#define DLN2_ADC_CHANNEL_GET_VAL DLN2_CMD(0x0A, DLN2_ADC_ID) > +#define DLN2_ADC_CHANNEL_GET_ALL_VAL DLN2_CMD(0x0B, DLN2_ADC_ID) > +#define DLN2_ADC_CHANNEL_SET_CFG DLN2_CMD(0x0C, DLN2_ADC_ID) > +#define DLN2_ADC_CHANNEL_GET_CFG DLN2_CMD(0x0D, DLN2_ADC_ID) > +#define DLN2_ADC_CONDITION_MET_EV DLN2_CMD(0x10, DLN2_ADC_ID) > + > +#define DLN2_ADC_EVENT_NONE 0 > +#define DLN2_ADC_EVENT_BELOW 1 > +#define DLN2_ADC_EVENT_LEVEL_ABOVE 2 > +#define DLN2_ADC_EVENT_OUTSIDE 3 > +#define DLN2_ADC_EVENT_INSIDE 4 > +#define DLN2_ADC_EVENT_ALWAYS 5 > + > +#define DLN2_ADC_MAX_CHANNELS 8 > +#define DLN2_ADC_DATA_BITS 10 > + > +/* > + * Plays similar role to iio_demux_table in subsystem core; except allocated > + * in a fixed 8-element array. > + */ > +struct dln2_adc_demux_table { > + unsigned int from; > + unsigned int to; > + unsigned int length; > +}; > + > +struct dln2_adc { > + struct platform_device *pdev; > + struct iio_chan_spec iio_channels[DLN2_ADC_MAX_CHANNELS + 1]; > + int port, trigger_chan; > + struct iio_trigger *trig; > + struct mutex mutex; > + /* Cached sample period in milliseconds */ > + unsigned int sample_period; > + /* Demux table */ > + unsigned int demux_count; > + struct dln2_adc_demux_table demux[DLN2_ADC_MAX_CHANNELS]; > + /* Precomputed timestamp padding offset and length */ > + unsigned int ts_pad_offset, ts_pad_length; > +}; > + > +struct dln2_adc_port_chan { > + u8 port; > + u8 chan; > +}; > + > +struct dln2_adc_get_all_vals { > + __le16 channel_mask; > + __le16 values[DLN2_ADC_MAX_CHANNELS]; > +}; > + > +static void dln2_adc_add_demux(struct dln2_adc *dln2, > + unsigned int in_loc, unsigned int out_loc, > + unsigned int length) > +{ > + struct dln2_adc_demux_table *p = dln2->demux_count ? > + &dln2->demux[dln2->demux_count - 1] : NULL; > + > + if (p && p->from + p->length == in_loc && > + p->to + p->length == out_loc) { > + p->length += length; > + } else if (dln2->demux_count < DLN2_ADC_MAX_CHANNELS) { > + p = &dln2->demux[dln2->demux_count++]; > + p->from = in_loc; > + p->to = out_loc; > + p->length = length; > + } > +} > + > +static void dln2_adc_update_demux(struct dln2_adc *dln2) > +{ > + int in_ind = -1, out_ind; > + unsigned int in_loc = 0, out_loc = 0; > + struct iio_dev *indio_dev = platform_get_drvdata(dln2->pdev); > + > + /* Clear out any old demux */ > + dln2->demux_count = 0; > + > + /* Optimize all 8-channels case */ > + if (indio_dev->masklength && > + (*indio_dev->active_scan_mask & 0xff) == 0xff) { > + dln2_adc_add_demux(dln2, 0, 0, 16); > + dln2->ts_pad_offset = 0; > + dln2->ts_pad_length = 0; > + return; > + } > + > + /* Build demux table from fixed 8-channels to active_scan_mask */ > + for_each_set_bit(out_ind, > + indio_dev->active_scan_mask, > + indio_dev->masklength) { > + /* Handle timestamp separately */ > + if (out_ind == DLN2_ADC_MAX_CHANNELS) > + break; > + for (++in_ind; in_ind != out_ind; ++in_ind) > + in_loc += 2; > + dln2_adc_add_demux(dln2, in_loc, out_loc, 2); > + out_loc += 2; > + in_loc += 2; > + } > + > + if (indio_dev->scan_timestamp) { > + size_t ts_offset = indio_dev->scan_bytes / sizeof(int64_t) - 1; > + > + dln2->ts_pad_offset = out_loc; > + dln2->ts_pad_length = ts_offset * sizeof(int64_t) - out_loc; > + } else { > + dln2->ts_pad_offset = 0; > + dln2->ts_pad_length = 0; > + } > +} > + > +static int dln2_adc_get_chan_count(struct dln2_adc *dln2) > +{ > + int ret; > + u8 port = dln2->port; > + u8 count; > + int olen = sizeof(count); > + > + ret = dln2_transfer(dln2->pdev, DLN2_ADC_GET_CHANNEL_COUNT, > + &port, sizeof(port), &count, &olen); > + if (ret < 0) { > + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); > + return ret; > + } > + if (olen < sizeof(count)) > + return -EPROTO; > + > + return count; > +} > + > +static int dln2_adc_set_port_resolution(struct dln2_adc *dln2) > +{ > + int ret; > + struct dln2_adc_port_chan port_chan = { > + .port = dln2->port, > + .chan = DLN2_ADC_DATA_BITS, > + }; > + > + ret = dln2_transfer_tx(dln2->pdev, DLN2_ADC_SET_RESOLUTION, > + &port_chan, sizeof(port_chan)); > + if (ret < 0) > + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); > + > + return ret; > +} > + > +static int dln2_adc_set_chan_enabled(struct dln2_adc *dln2, > + int channel, bool enable) > +{ > + int ret; > + struct dln2_adc_port_chan port_chan = { > + .port = dln2->port, > + .chan = channel, > + }; > + u16 cmd = enable ? DLN2_ADC_CHANNEL_ENABLE : DLN2_ADC_CHANNEL_DISABLE; > + > + ret = dln2_transfer_tx(dln2->pdev, cmd, &port_chan, sizeof(port_chan)); > + if (ret < 0) > + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); > + > + return ret; > +} > + > +static int dln2_adc_set_port_enabled(struct dln2_adc *dln2, bool enable, > + u16 *conflict_out) > +{ > + int ret; > + u8 port = dln2->port; > + __le16 conflict; > + int olen = sizeof(conflict); > + u16 cmd = enable ? DLN2_ADC_ENABLE : DLN2_ADC_DISABLE; > + > + if (conflict_out) > + *conflict_out = 0; > + > + ret = dln2_transfer(dln2->pdev, cmd, &port, sizeof(port), > + &conflict, &olen); > + if (ret < 0) { > + dev_dbg(&dln2->pdev->dev, "Problem in %s(%d)\n", > + __func__, (int)enable); > + if (conflict_out && enable && olen >= sizeof(conflict)) > + *conflict_out = le16_to_cpu(conflict); > + return ret; > + } > + if (enable && olen < sizeof(conflict)) > + return -EPROTO; > + > + return ret; > +} > + > +static int dln2_adc_set_chan_period(struct dln2_adc *dln2, > + unsigned int channel, unsigned int period) > +{ > + int ret; > + struct { > + struct dln2_adc_port_chan port_chan; > + __u8 type; > + __le16 period; > + __le16 low; > + __le16 high; > + } __packed set_cfg = { > + .port_chan.port = dln2->port, > + .port_chan.chan = channel, > + .type = period ? DLN2_ADC_EVENT_ALWAYS : DLN2_ADC_EVENT_NONE, > + .period = cpu_to_le16(period) > + }; > + > + ret = dln2_transfer_tx(dln2->pdev, DLN2_ADC_CHANNEL_SET_CFG, > + &set_cfg, sizeof(set_cfg)); > + if (ret < 0) > + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); > + > + return ret; > +} > + > +static int dln2_adc_read(struct dln2_adc *dln2, unsigned int channel) > +{ > + int ret, i; > + struct iio_dev *indio_dev = platform_get_drvdata(dln2->pdev); > + u16 conflict; > + __le16 value; > + int olen = sizeof(value); > + struct dln2_adc_port_chan port_chan = { > + .port = dln2->port, > + .chan = channel, > + }; > + > + ret = iio_device_claim_direct_mode(indio_dev); > + if (ret < 0) > + return ret; > + > + ret = dln2_adc_set_chan_enabled(dln2, channel, true); > + if (ret < 0) > + goto release_direct; > + > + ret = dln2_adc_set_port_enabled(dln2, true, &conflict); > + if (ret < 0) { > + if (conflict) { > + dev_err(&dln2->pdev->dev, > + "ADC pins conflict with mask %04X\n", > + (int)conflict); > + ret = -EBUSY; > + } > + goto disable_chan; > + } > + > + /* > + * Call GET_VAL twice due to initial zero-return immediately after > + * enabling channel. > + */ > + for (i = 0; i < 2; ++i) { > + ret = dln2_transfer(dln2->pdev, DLN2_ADC_CHANNEL_GET_VAL, > + &port_chan, sizeof(port_chan), > + &value, &olen); > + if (ret < 0) { > + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); > + goto disable_port; > + } > + if (olen < sizeof(value)) { > + ret = -EPROTO; > + goto disable_port; > + } > + } > + > + ret = le16_to_cpu(value); > + > +disable_port: > + dln2_adc_set_port_enabled(dln2, false, NULL); > +disable_chan: > + dln2_adc_set_chan_enabled(dln2, channel, false); > +release_direct: > + iio_device_release_direct_mode(indio_dev); > + > + return ret; > +} > + > +static int dln2_adc_read_all(struct dln2_adc *dln2, > + struct dln2_adc_get_all_vals *get_all_vals) > +{ > + int ret; > + __u8 port = dln2->port; > + int olen = sizeof(*get_all_vals); > + > + ret = dln2_transfer(dln2->pdev, DLN2_ADC_CHANNEL_GET_ALL_VAL, > + &port, sizeof(port), get_all_vals, &olen); > + if (ret < 0) { > + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); > + return ret; > + } > + if (olen < sizeof(*get_all_vals)) > + return -EPROTO; > + > + return ret; > +} > + > +static int dln2_adc_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, > + int *val2, > + long mask) > +{ > + int ret; > + unsigned int microhertz; > + struct dln2_adc *dln2 = iio_priv(indio_dev); > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + mutex_lock(&dln2->mutex); > + ret = dln2_adc_read(dln2, chan->channel); > + mutex_unlock(&dln2->mutex); > + > + if (ret < 0) > + return ret; > + > + *val = ret; > + return IIO_VAL_INT; > + > + case IIO_CHAN_INFO_SCALE: > + /* > + * Voltage reference is fixed at 3.3v > + * 3.3 / (1 << 10) * 1000000000 > + */ > + *val = 0; > + *val2 = 3222656; > + return IIO_VAL_INT_PLUS_NANO; > + > + case IIO_CHAN_INFO_SAMP_FREQ: > + if (dln2->sample_period) { > + microhertz = 1000000000 / dln2->sample_period; > + *val = microhertz / 1000000; > + *val2 = microhertz % 1000000; > + } else { > + *val = 0; > + *val2 = 0; > + } > + > + return IIO_VAL_INT_PLUS_MICRO; > + > + default: > + return -EINVAL; > + } > +} > + > +static int dln2_adc_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, > + int val2, > + long mask) > +{ > + int ret; > + unsigned int microhertz; > + struct dln2_adc *dln2 = iio_priv(indio_dev); > + > + switch (mask) { > + case IIO_CHAN_INFO_SAMP_FREQ: > + microhertz = 1000000 * val + val2; > + > + mutex_lock(&dln2->mutex); > + > + dln2->sample_period = > + microhertz ? 1000000000 / microhertz : UINT_MAX; > + if (dln2->sample_period > 65535) { > + dln2->sample_period = 65535; > + dev_warn(&dln2->pdev->dev, > + "clamping period to 65535ms\n"); > + } > + > + /* > + * The first requested channel is arbitrated as a shared > + * trigger source, so only one event is registered with the > + * DLN. The event handler will then read all enabled channel > + * values using DLN2_ADC_CHANNEL_GET_ALL_VAL to maintain > + * synchronization between ADC readings. > + */ > + if (dln2->trigger_chan != -1) > + ret = dln2_adc_set_chan_period(dln2, > + dln2->trigger_chan, dln2->sample_period); > + else > + ret = 0; > + > + mutex_unlock(&dln2->mutex); > + > + return ret; > + > + default: > + return -EINVAL; > + } > +} > + > +static int dln2_update_scan_mode(struct iio_dev *indio_dev, > + const unsigned long *scan_mask) > +{ > + struct dln2_adc *dln2 = iio_priv(indio_dev); > + int chan_count = indio_dev->num_channels - 1; > + int ret, i, j; > + > + mutex_lock(&dln2->mutex); > + > + for (i = 0; i < chan_count; ++i) { > + ret = dln2_adc_set_chan_enabled(dln2, i, > + test_bit(i, scan_mask)); > + if (ret < 0) { > + for (j = 0; j < i; ++j) > + dln2_adc_set_chan_enabled(dln2, j, false); > + mutex_unlock(&dln2->mutex); > + dev_err(&dln2->pdev->dev, > + "Unable to enable ADC channel %d\n", i); > + return -EBUSY; > + } > + } > + > + dln2_adc_update_demux(dln2); > + > + mutex_unlock(&dln2->mutex); > + > + return 0; > +} > + > +#define DLN2_ADC_CHAN(lval, idx) { \ > + lval.type = IIO_VOLTAGE; \ > + lval.channel = idx; \ > + lval.indexed = 1; \ > + lval.info_mask_separate = BIT(IIO_CHAN_INFO_RAW); \ > + lval.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) | \ > + BIT(IIO_CHAN_INFO_SAMP_FREQ); \ > + lval.scan_index = idx; \ > + lval.scan_type.sign = 'u'; \ > + lval.scan_type.realbits = DLN2_ADC_DATA_BITS; \ > + lval.scan_type.storagebits = 16; \ > + lval.scan_type.endianness = IIO_LE; \ > +} > + > +/* Assignment version of IIO_CHAN_SOFT_TIMESTAMP */ > +#define IIO_CHAN_SOFT_TIMESTAMP_ASSIGN(lval, _si) { \ > + lval.type = IIO_TIMESTAMP; \ > + lval.channel = -1; \ > + lval.scan_index = _si; \ > + lval.scan_type.sign = 's'; \ > + lval.scan_type.realbits = 64; \ > + lval.scan_type.storagebits = 64; \ > +} > + > +static const struct iio_info dln2_adc_info = { > + .read_raw = dln2_adc_read_raw, > + .write_raw = dln2_adc_write_raw, > + .update_scan_mode = dln2_update_scan_mode, > + .driver_module = THIS_MODULE, > +}; > + > +static irqreturn_t dln2_adc_trigger_h(int irq, void *p) > +{ > + struct iio_poll_func *pf = p; > + struct iio_dev *indio_dev = pf->indio_dev; > + struct { > + __le16 values[DLN2_ADC_MAX_CHANNELS]; > + int64_t timestamp_space; > + } data; > + struct dln2_adc_get_all_vals dev_data; > + struct dln2_adc *dln2 = iio_priv(indio_dev); > + const struct dln2_adc_demux_table *t; > + int ret, i; > + > + mutex_lock(&dln2->mutex); > + ret = dln2_adc_read_all(dln2, &dev_data); > + mutex_unlock(&dln2->mutex); > + if (ret < 0) > + goto done; > + > + /* Demux operation */ > + for (i = 0; i < dln2->demux_count; ++i) { > + t = &dln2->demux[i]; > + memcpy((void *)data.values + t->to, > + (void *)dev_data.values + t->from, t->length); > + } > + > + /* Zero padding space between values and timestamp */ > + if (dln2->ts_pad_length) > + memset((void *)data.values + dln2->ts_pad_offset, > + 0, dln2->ts_pad_length); > + > + iio_push_to_buffers_with_timestamp(indio_dev, &data, > + iio_get_time_ns(indio_dev)); > + > +done: > + iio_trigger_notify_done(indio_dev->trig); > + return IRQ_HANDLED; > +} > + > +static int dln2_adc_triggered_buffer_postenable(struct iio_dev *indio_dev) > +{ > + int ret; > + struct dln2_adc *dln2 = iio_priv(indio_dev); > + u16 conflict; > + unsigned int trigger_chan; > + > + mutex_lock(&dln2->mutex); > + > + /* Enable ADC */ > + ret = dln2_adc_set_port_enabled(dln2, true, &conflict); > + if (ret < 0) { > + mutex_unlock(&dln2->mutex); > + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); > + if (conflict) { > + dev_err(&dln2->pdev->dev, > + "ADC pins conflict with mask %04X\n", > + (int)conflict); > + ret = -EBUSY; > + } > + return ret; > + } > + > + /* Assign trigger channel based on first enabled channel */ > + trigger_chan = find_first_bit(indio_dev->active_scan_mask, > + indio_dev->masklength); > + if (trigger_chan < DLN2_ADC_MAX_CHANNELS) { > + dln2->trigger_chan = trigger_chan; > + ret = dln2_adc_set_chan_period(dln2, dln2->trigger_chan, > + dln2->sample_period); > + mutex_unlock(&dln2->mutex); > + if (ret < 0) { > + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); > + return ret; > + } > + } else { > + dln2->trigger_chan = -1; > + mutex_unlock(&dln2->mutex); > + } > + > + return iio_triggered_buffer_postenable(indio_dev); > +} > + > +static int dln2_adc_triggered_buffer_predisable(struct iio_dev *indio_dev) > +{ > + int ret; > + struct dln2_adc *dln2 = iio_priv(indio_dev); > + > + mutex_lock(&dln2->mutex); > + > + /* Disable trigger channel */ > + if (dln2->trigger_chan != -1) { > + dln2_adc_set_chan_period(dln2, dln2->trigger_chan, 0); > + dln2->trigger_chan = -1; > + } > + > + /* Disable ADC */ > + ret = dln2_adc_set_port_enabled(dln2, false, NULL); > + > + mutex_unlock(&dln2->mutex); > + if (ret < 0) { > + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); > + return ret; > + } > + > + return iio_triggered_buffer_predisable(indio_dev); > +} > + > +static const struct iio_buffer_setup_ops dln2_adc_buffer_setup_ops = { > + .postenable = dln2_adc_triggered_buffer_postenable, > + .predisable = dln2_adc_triggered_buffer_predisable, > +}; > + > +static void dln2_adc_event(struct platform_device *pdev, u16 echo, > + const void *data, int len) > +{ > + struct iio_dev *indio_dev = platform_get_drvdata(pdev); > + struct dln2_adc *dln2 = iio_priv(indio_dev); > + > + /* Called via URB completion handler */ > + iio_trigger_poll(dln2->trig); > +} > + > +static const struct iio_trigger_ops dln2_adc_trigger_ops = { > + .owner = THIS_MODULE, > +}; > + > +static int dln2_adc_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct dln2_adc *dln2; > + struct dln2_platform_data *pdata = dev_get_platdata(&pdev->dev); > + struct iio_dev *indio_dev; > + int i, ret, chans; > + > + indio_dev = devm_iio_device_alloc(dev, sizeof(*dln2)); > + if (!indio_dev) { > + dev_err(dev, "failed allocating iio device\n"); > + return -ENOMEM; > + } > + > + dln2 = iio_priv(indio_dev); > + dln2->pdev = pdev; > + dln2->port = pdata->port; > + dln2->trigger_chan = -1; > + mutex_init(&dln2->mutex); > + > + platform_set_drvdata(pdev, indio_dev); > + > + ret = dln2_adc_set_port_resolution(dln2); > + if (ret < 0) { > + dev_err(dev, "failed to set ADC resolution to 10 bits\n"); > + return ret; > + } > + > + chans = dln2_adc_get_chan_count(dln2); > + if (chans < 0) { > + dev_err(dev, "failed to get channel count: %d\n", chans); > + return chans; > + } > + if (chans > DLN2_ADC_MAX_CHANNELS) { > + chans = DLN2_ADC_MAX_CHANNELS; > + dev_warn(dev, "clamping channels to %d\n", > + DLN2_ADC_MAX_CHANNELS); > + } > + > + for (i = 0; i < chans; ++i) > + DLN2_ADC_CHAN(dln2->iio_channels[i], i) > + IIO_CHAN_SOFT_TIMESTAMP_ASSIGN(dln2->iio_channels[i], i); > + > + indio_dev->name = DLN2_ADC_MOD_NAME; > + indio_dev->dev.parent = dev; > + indio_dev->info = &dln2_adc_info; > + indio_dev->modes = INDIO_DIRECT_MODE; > + indio_dev->channels = dln2->iio_channels; > + indio_dev->num_channels = chans + 1; > + indio_dev->setup_ops = &dln2_adc_buffer_setup_ops; > + > + dln2->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", > + indio_dev->name, indio_dev->id); > + if (!dln2->trig) { > + dev_err(dev, "failed to allocate trigger\n"); > + return -ENOMEM; > + } > + dln2->trig->ops = &dln2_adc_trigger_ops; > + iio_trigger_set_drvdata(dln2->trig, dln2); > + devm_iio_trigger_register(dev, dln2->trig); > + iio_trigger_set_immutable(indio_dev, dln2->trig); > + > + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, > + dln2_adc_trigger_h, > + &dln2_adc_buffer_setup_ops); > + if (ret) { > + dev_err(dev, "failed to allocate triggered buffer: %d\n", ret); > + return ret; > + } > + > + ret = dln2_register_event_cb(pdev, DLN2_ADC_CONDITION_MET_EV, > + dln2_adc_event); > + if (ret) { > + dev_err(dev, "failed to setup DLN2 periodic event: %d\n", ret); > + return ret; > + } > + > + ret = iio_device_register(indio_dev); > + if (ret) { > + dev_err(dev, "failed to register iio device: %d\n", ret); > + goto unregister_event; > + } > + > + return ret; > + > +unregister_event: > + dln2_unregister_event_cb(pdev, DLN2_ADC_CONDITION_MET_EV); > + > + return ret; > +} > + > +static int dln2_adc_remove(struct platform_device *pdev) > +{ > + struct iio_dev *indio_dev = platform_get_drvdata(pdev); > + > + iio_device_unregister(indio_dev); > + dln2_unregister_event_cb(pdev, DLN2_ADC_CONDITION_MET_EV); > + return 0; > +} > + > +static struct platform_driver dln2_adc_driver = { > + .driver.name = DLN2_ADC_MOD_NAME, > + .probe = dln2_adc_probe, > + .remove = dln2_adc_remove, > +}; > + > +module_platform_driver(dln2_adc_driver); > + > +MODULE_AUTHOR("Jack Andersen <jackoalan@xxxxxxxxx"); > +MODULE_DESCRIPTION("Driver for the Diolan DLN2 ADC interface"); > +MODULE_LICENSE("GPL v2"); > +MODULE_ALIAS("platform:dln2-adc"); -- 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