From patchwork Fri Apr 21 14:58:23 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?M=C3=A5rten_Lindahl?= X-Patchwork-Id: 753471 Return-Path: X-Original-To: incoming-dt@patchwork.ozlabs.org Delivered-To: patchwork-incoming-dt@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3w8fxM5vlwz9ryv for ; Sat, 22 Apr 2017 01:37:11 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1161425AbdDUPg1 (ORCPT ); Fri, 21 Apr 2017 11:36:27 -0400 Received: from bastet.se.axis.com ([195.60.68.11]:48410 "EHLO bastet.se.axis.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1161350AbdDUPgZ (ORCPT ); Fri, 21 Apr 2017 11:36:25 -0400 Received: from localhost (localhost [127.0.0.1]) by bastet.se.axis.com (Postfix) with ESMTP id 514DF18133; Fri, 21 Apr 2017 16:58:46 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at bastet.se.axis.com Received: from bastet.se.axis.com ([IPv6:::ffff:127.0.0.1]) by localhost (bastet.se.axis.com [::ffff:127.0.0.1]) (amavisd-new, port 10024) with LMTP id 8d7GgZ84nFvT; Fri, 21 Apr 2017 16:58:44 +0200 (CEST) Received: from boulder02.se.axis.com (boulder02.se.axis.com [10.0.8.16]) by bastet.se.axis.com (Postfix) with ESMTPS id 466F318114; Fri, 21 Apr 2017 16:58:44 +0200 (CEST) Received: from boulder02.se.axis.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 2A7E61A135; Fri, 21 Apr 2017 16:58:44 +0200 (CEST) Received: from boulder02.se.axis.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 1541E1A134; Fri, 21 Apr 2017 16:58:44 +0200 (CEST) Received: from thoth.se.axis.com (unknown [10.0.2.173]) by boulder02.se.axis.com (Postfix) with ESMTP; Fri, 21 Apr 2017 16:58:44 +0200 (CEST) Received: from lnxmartenli.se.axis.com (lnxmartenli.se.axis.com [10.94.71.1]) by thoth.se.axis.com (Postfix) with ESMTP id 081941A5E; Fri, 21 Apr 2017 16:58:44 +0200 (CEST) Received: by lnxmartenli.se.axis.com (Postfix, from userid 21033) id 02436409D3; Fri, 21 Apr 2017 16:58:43 +0200 (CEST) From: =?UTF-8?q?M=C3=A5rten=20Lindahl?= To: jic23@kernel.org Cc: knaack.h@gmx.de, lars@metafoo.de, pmeerw@pmeerw.net, linux-iio@vger.kernel.org, robh+dt@kernel.org, mark.rutland@arm.com, devicetree@vger.kernel.org, =?UTF-8?q?M=C3=A5rten=20Lindahl?= Subject: [PATCH] iio: adc: add driver for the ti-adc084s021 chip Date: Fri, 21 Apr 2017 16:58:23 +0200 Message-Id: <1492786703-5149-1-git-send-email-marten.lindahl@axis.com> X-Mailer: git-send-email 2.1.4 MIME-Version: 1.0 X-TM-AS-GCONF: 00 Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org From: Mårten Lindahl This adds support for the Texas Instruments ADC084S021 ADC chip. Signed-off-by: Mårten Lindahl --- .../devicetree/bindings/iio/adc/ti-adc084s021.txt | 25 ++ drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-adc084s021.c | 342 +++++++++++++++++++++ 4 files changed, 380 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti-adc084s021.txt create mode 100644 drivers/iio/adc/ti-adc084s021.c diff --git a/Documentation/devicetree/bindings/iio/adc/ti-adc084s021.txt b/Documentation/devicetree/bindings/iio/adc/ti-adc084s021.txt new file mode 100644 index 0000000..921eb46 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti-adc084s021.txt @@ -0,0 +1,25 @@ +* Texas Instruments' ADC084S021 + +Required properties: + - compatible : Must be "ti,adc084s021" + - reg : SPI chip select number for the device + - vref-supply : The regulator supply for ADC reference voltage + - spi-max-frequency : Definition as per Documentation/devicetree/bindings/spi/spi-bus.txt + +Optional properties: + - spi-cpol : SPI inverse clock polarity, as per spi-bus bindings + - spi-cpha : SPI shifted clock phase (CPHA), as per spi-bus bindings + - spi-cs-high : SPI chip select active high, as per spi-bus bindings + + +Example: +adc@0 { + compatible = "ti,adc084s021"; + reg = <0>; + vref-supply = <&adc_vref>; + spi-cpol; + spi-cpha; + spi-cs-high; + spi-max-frequency = <16000000>; + pl022,com-mode = <0x2>; /* DMA */ +}; diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index dedae7a..13141e5 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -560,6 +560,18 @@ config TI_ADC0832 This driver can also be built as a module. If so, the module will be called ti-adc0832. +config TI_ADC084S021 + tristate "Texas Instruments ADC084S021" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADC084S021 + chips. + + This driver can also be built as a module. If so, the module will be + called ti-adc084s021. + config TI_ADC12138 tristate "Texas Instruments ADC12130/ADC12132/ADC12138" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d001262..b1a6158 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o +obj-$(CONFIG_TI_ADC084S021) += ti-adc084s021.o obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o diff --git a/drivers/iio/adc/ti-adc084s021.c b/drivers/iio/adc/ti-adc084s021.c new file mode 100644 index 0000000..4f33b91 --- /dev/null +++ b/drivers/iio/adc/ti-adc084s021.c @@ -0,0 +1,342 @@ +/** + * Copyright (C) 2017 Axis Communications AB + * + * Driver for Texas Instruments' ADC084S021 ADC chip. + * Datasheets can be found here: + * http://www.ti.com/lit/ds/symlink/adc084s021.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "adc084s021" +#define DRIVER_VERSION "1.0" +#define ADC_RESOLUTION 8 +#define ADC_N_CHANNELS 4 + +struct adc084s021_configuration { + const struct iio_chan_spec *channels; + u8 num_channels; +}; + +struct adc084s021 { + struct spi_device *spi; + struct spi_message message; + struct spi_transfer spi_trans[2]; + struct regulator *reg; + struct mutex lock; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + u8 tx_buf[2]; + u8 rx_buf[2]; + } ____cacheline_aligned; + u8 cur_adc_values[ADC_N_CHANNELS]; +}; + +/** + * Event triggered when value changes on a channel + */ +static const struct iio_event_spec adc084s021_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, +}; + +/** + * Channel specification + */ +#define ADC084S021_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .channel = (num), \ + .address = (num << 3), \ + .indexed = 1, \ + .scan_index = num, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 32, \ + .shift = 24 - ((num << 3)), \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .event_spec = &adc084s021_event, \ + .num_event_specs = 1, \ + } + +static const struct iio_chan_spec adc084s021_channels[] = { + ADC084S021_VOLTAGE_CHANNEL(0), + ADC084S021_VOLTAGE_CHANNEL(1), + ADC084S021_VOLTAGE_CHANNEL(2), + ADC084S021_VOLTAGE_CHANNEL(3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct adc084s021_configuration adc084s021_config[] = { + { adc084s021_channels, ARRAY_SIZE(adc084s021_channels) }, +}; + +/** + * Read an ADC channel and return its value. + * + * @adc: The ADC SPI data. + * @channel: The IIO channel data structure. + */ +static int adc084s021_adc_conversion(struct adc084s021 *adc, + struct iio_chan_spec const *channel) +{ + u16 value; + int ret; + + mutex_lock(&adc->lock); + adc->tx_buf[0] = channel->address; + + /* Do the transfer */ + ret = spi_sync(adc->spi, &adc->message); + + if (ret < 0) { + mutex_unlock(&adc->lock); + return ret; + } + + value = (adc->rx_buf[0] << 4) | (adc->rx_buf[1] >> 4); + mutex_unlock(&adc->lock); + + dev_dbg(&adc->spi->dev, "value 0x%02X on channel %d\n", + value, channel->channel); + return value; +} + +/** + * Make a readout of requested IIO channel info. + * + * @indio_dev: The industrial I/O device. + * @channel: The IIO channel data structure. + * @val: First element of value (integer). + * @val2: Second element of value (fractional). + * @mask: The info_mask to read. + */ +static int adc084s021_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct adc084s021 *adc = iio_priv(indio_dev); + int retval; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + retval = adc084s021_adc_conversion(adc, channel); + if (retval < 0) + return retval; + + *val = retval; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +/** + * Read enabled ADC channels and push data to the buffer. + * + * @irq: The interrupt number (not used). + * @pollfunc: Pointer to the poll func. + */ +static irqreturn_t adc084s021_trigger_handler(int irq, void *pollfunc) +{ + struct iio_poll_func *pf = pollfunc; + struct iio_dev *indio_dev = pf->indio_dev; + struct adc084s021 *adc = iio_priv(indio_dev); + u8 *data; + s64 timestamp; + int value, scan_index; + + data = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!data) { + iio_trigger_notify_done(indio_dev->trig); + return IRQ_NONE; + } + + timestamp = iio_get_time_ns(indio_dev); + + for_each_set_bit(scan_index, indio_dev->active_scan_mask, + indio_dev->masklength) { + const struct iio_chan_spec *channel = + &indio_dev->channels[scan_index]; + value = adc084s021_adc_conversion(adc, channel); + data[scan_index] = value; + + /* + * Compare read data to previous read. If it differs send + * event notification for affected channel. + */ + if (adc->cur_adc_values[scan_index] != (u8)value) { + adc->cur_adc_values[scan_index] = (u8)value; + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_NO_MOD, IIO_EV_DIR_NONE, + IIO_EV_TYPE_CHANGE, + channel->channel, 0, 0), + timestamp); + dev_dbg(&indio_dev->dev, + "new value on ch%d: 0x%02X (ts %llu)\n", + channel->channel, value, timestamp); + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, data, timestamp); + iio_trigger_notify_done(indio_dev->trig); + kfree(data); + return IRQ_HANDLED; +} + +static const struct iio_info adc084s021_info = { + .read_raw = adc084s021_read_raw, + .driver_module = THIS_MODULE, +}; + +/** + * Create and register ADC IIO device for SPI. + */ +static int adc084s021_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adc084s021 *adc; + int config = spi_get_device_id(spi)->driver_data; + int retval; + + /* Allocate an Industrial I/O device */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) { + dev_err(&spi->dev, "Failed to allocate IIO device\n"); + return -ENOMEM; + } + + adc = iio_priv(indio_dev); + adc->spi = spi; + spi->bits_per_word = ADC_RESOLUTION; + + /* Update the SPI device with config and connect the iio dev */ + retval = spi_setup(spi); + if (retval) { + dev_err(&spi->dev, "Failed to update SPI device\n"); + return retval; + } + spi_set_drvdata(spi, indio_dev); + + /* Initiate the Industrial I/O device */ + indio_dev->dev.parent = &spi->dev; + indio_dev->dev.of_node = spi->dev.of_node; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &adc084s021_info; + indio_dev->channels = adc084s021_config[config].channels; + indio_dev->num_channels = adc084s021_config[config].num_channels; + + /* Create SPI transfer for channel reads */ + adc->spi_trans[0].tx_buf = &adc->tx_buf[0]; + adc->spi_trans[0].len = 2; + adc->spi_trans[0].speed_hz = spi->max_speed_hz; + adc->spi_trans[0].bits_per_word = spi->bits_per_word; + adc->spi_trans[1].rx_buf = &adc->rx_buf[0]; + adc->spi_trans[1].len = 2; + adc->spi_trans[1].speed_hz = spi->max_speed_hz; + adc->spi_trans[1].bits_per_word = spi->bits_per_word; + + /* Setup SPI message for channel reads */ + spi_message_init(&adc->message); + spi_message_add_tail(&adc->spi_trans[0], &adc->message); + spi_message_add_tail(&adc->spi_trans[1], &adc->message); + + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return PTR_ERR(adc->reg); + + retval = regulator_enable(adc->reg); + if (retval < 0) + return retval; + + mutex_init(&adc->lock); + + /* Setup triggered buffer with pollfunction */ + retval = iio_triggered_buffer_setup(indio_dev, NULL, + adc084s021_trigger_handler, NULL); + if (retval) { + dev_err(&spi->dev, "Failed to setup triggered buffer\n"); + goto buffer_setup_failed; + } + + retval = iio_device_register(indio_dev); + if (retval) { + dev_err(&spi->dev, "Failed to register IIO device\n"); + goto device_register_failed; + } + + dev_info(&spi->dev, "probed!\n"); + return 0; + +device_register_failed: + iio_triggered_buffer_cleanup(indio_dev); +buffer_setup_failed: + regulator_disable(adc->reg); + return retval; +} + +/** + * Unregister ADC IIO device for SPI. + */ +static int adc084s021_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adc084s021 *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(adc->reg); + return 0; +} + +static const struct of_device_id adc084s021_of_match[] = { + { .compatible = "ti,adc084s021", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, adc084s021_of_match); + +static const struct spi_device_id adc084s021_id[] = { + { MODULE_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(spi, adc084s021_id); + +static struct spi_driver adc084s021_driver = { + .driver = { + .name = MODULE_NAME, + .of_match_table = of_match_ptr(adc084s021_of_match), + }, + .probe = adc084s021_probe, + .remove = adc084s021_remove, + .id_table = adc084s021_id, +}; + +module_spi_driver(adc084s021_driver); + +MODULE_AUTHOR("Mårten Lindahl "); +MODULE_DESCRIPTION("Texas Instruments ADC084S021"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRIVER_VERSION);