From patchwork Wed Jan 11 06:51:11 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Reid X-Patchwork-Id: 713620 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 3tz01G68rQz9vDZ for ; Wed, 11 Jan 2017 17:51:46 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752624AbdAKGv0 (ORCPT ); Wed, 11 Jan 2017 01:51:26 -0500 Received: from anchovy3.45ru.net.au ([203.30.46.155]:51730 "EHLO anchovy.45ru.net.au" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S933148AbdAKGvX (ORCPT ); Wed, 11 Jan 2017 01:51:23 -0500 Received: (qmail 13647 invoked by uid 5089); 11 Jan 2017 06:51:19 -0000 Received: by simscan 1.2.0 ppid: 13625, pid: 13627, t: 0.0698s scanners: regex: 1.2.0 attach: 1.2.0 clamav: 0.88.3/m:40/d:1950 Received: from unknown (HELO preid-centos7.electromag.com.au) (preid@electromag.com.au@203.59.230.133) by anchovy2.45ru.net.au with ESMTPA; 11 Jan 2017 06:51:19 -0000 Received: by preid-centos7.electromag.com.au (Postfix, from userid 1000) id 32B9E3044E851; Wed, 11 Jan 2017 14:51:14 +0800 (AWST) From: Phil Reid To: jic23@kernel.org, knaack.h@gmx.de, lars@metafoo.de, pmeerw@pmeerw.net, robh+dt@kernel.org, mark.rutland@arm.com, preid@electromag.com.au, linux-iio@vger.kernel.org, devicetree@vger.kernel.org Subject: [PATCH v1 1/1] iio: adc: tlc4541: add support for TI tlc4541 adc Date: Wed, 11 Jan 2017 14:51:11 +0800 Message-Id: <1484117471-67082-1-git-send-email-preid@electromag.com.au> X-Mailer: git-send-email 1.8.3.1 Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org This adds TI's tlc4541 16-bit ADC driver. Which is a single channel ADC. Supports raw and trigger buffer access. Also supports the tlc3541 14-bit device, which has not been tested. Implementation of the tlc3541 is fairly straight forward thou. Signed-off-by: Phil Reid Acked-by: Rob Herring --- Notes: Changes from v1: - Add tlc3541 support and chan spec. - remove fields that where already 0 from TLC4541_V_CHAN macro - Increase rx_buf size in tlc4541_state to avoid copy in tlc4541_trigger_handle - Remove erroneous be16_to_cpu in tlc4541_trigger_handle - Docs/binding: spi -> SPI & add ti,tlc3541 I haven't add Rob's Ack due to adding a new compatible string. I tried to ".index = 1" from the spec as suggested by Peter, but that didn't seem to work. Perhaps remove of .channel was the intended target. Example output from iio_readdev with ".index = 1" root@cyclone5:~# mkdir /sys/kernel/config/iio/triggers/hrtimer/hr1 root@cyclone5:~# iio_readdev -t hr1 -b 32 -s 10 tlc4541 | hexdump WARNING: High-speed mode not enabled 0000000 af00 0000 0000 0000 b922 ca99 93da 1492 0000010 a800 00ff 0000 0000 b246 cb30 93da 1492 0000020 a900 0000 0000 0000 4f9c cbc9 93da 1492 0000030 aa00 00ff 0000 0000 bd2c cc61 93da 1492 0000040 aa00 00ff 0000 0000 544c ccfa 93da 1492 0000050 ab00 00ff 0000 0000 e806 cd92 93da 1492 0000060 a900 00ff 0000 0000 846c ce2b 93da 1492 0000070 ab00 0000 0000 0000 2efc cec8 93da 1492 0000080 a800 00ff 0000 0000 b090 cf5c 93da 1492 0000090 a900 00ff 0000 0000 476a cff5 93da 1492 without .index root@cyclone5:~# mkdir /sys/kernel/config/iio/triggers/hrtimer/hr1 root@cyclone5:~# iio_readdev -t hr1 -b 32 -s 10 tlc4541 | hexdump WARNING: High-speed mode not enabled 0000000 6db0 eeb6 93e3 1492 35e0 ef4f 93e3 1492 0000010 4b34 efe5 93e3 1492 e9f2 f07d 93e3 1492 0000020 6182 f116 93e3 1492 090a f1af 93e3 1492 0000030 409c f249 93e3 1492 6c1a f2e0 93e3 1492 0000040 cd02 f378 93e3 1492 9582 f411 93e3 1492 .../devicetree/bindings/iio/adc/ti-tlc4541.txt | 17 ++ drivers/iio/adc/Kconfig | 11 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-tlc4541.c | 276 +++++++++++++++++++++ 4 files changed, 305 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti-tlc4541.txt create mode 100644 drivers/iio/adc/ti-tlc4541.c diff --git a/Documentation/devicetree/bindings/iio/adc/ti-tlc4541.txt b/Documentation/devicetree/bindings/iio/adc/ti-tlc4541.txt new file mode 100644 index 0000000..e1de2bd --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti-tlc4541.txt @@ -0,0 +1,17 @@ +* Texas Instruments' TLC4541 + +Required properties: + - compatible: Should be one of + * "ti,tlc4541" + * "ti,tlc3541" + - reg: SPI chip select number for the device + - vref-supply: The regulator supply for ADC reference voltage + - spi-max-frequency: Max SPI frequency to use (<= 200000) + +Example: +adc@0 { + compatible = "ti,adc0832"; + reg = <0>; + vref-supply = <&vdd_supply>; + spi-max-frequency = <200000>; +}; diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 99c0514..4dda3f0 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -525,6 +525,17 @@ config TI_AM335X_ADC To compile this driver as a module, choose M here: the module will be called ti_am335x_adc. +config TI_TLC4541 + tristate "Texas Instruments TLC4541 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Texas Instruments TLC4541 ADC chip. + + This driver can also be built as a module. If so, the module will be + called ti-tlc4541. + config TWL4030_MADC tristate "TWL4030 MADC (Monitoring A/D Converter)" depends on TWL4030_CORE diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 7a40c04..9bf2377 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o +obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o obj-$(CONFIG_VF610_ADC) += vf610_adc.o diff --git a/drivers/iio/adc/ti-tlc4541.c b/drivers/iio/adc/ti-tlc4541.c new file mode 100644 index 0000000..a0cd5e1 --- /dev/null +++ b/drivers/iio/adc/ti-tlc4541.c @@ -0,0 +1,276 @@ +/* + * TI tlc4541 ADC Driver + * + * Copyright (C) 2017 Phil Reid + * + * Datasheets can be found here: + * http://www.ti.com/lit/gpn/tlc3541 + * http://www.ti.com/lit/gpn/tlc4541 + * + * 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. + * + * The tlc4541 requires 24 clock cycles to start a transfer. + * Conversion then takes 2.94us to complete before data is ready + * Data is returned MSB first. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct tlc4541_state { + struct spi_device *spi; + struct regulator *reg; + struct spi_transfer scan_single_xfer[3]; + struct spi_message scan_single_msg; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + * 2 bytes data + 6 bytes padding + 8 bytes timestamp when + * call iio_push_to_buffers_with_timestamp. + */ + __be16 rx_buf[8] ____cacheline_aligned; +}; + +struct tlc4541_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +enum tlc4541_id { + TLC3541, + TLC4541, +}; + +#define TLC4541_V_CHAN(bits, bitshift) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = bitshift, \ + .endianness = IIO_BE, \ + }, \ + } + +#define DECLARE_TLC4541_CHANNELS(name, bits, bitshift) \ +const struct iio_chan_spec name ## _channels[] = { \ + TLC4541_V_CHAN(bits, bitshift), \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ +} + +static DECLARE_TLC4541_CHANNELS(tlc4541, 16, 0); +static DECLARE_TLC4541_CHANNELS(tlc3541, 14, 2); + +static const struct tlc4541_chip_info tlc4541_chip_info[] = { + [TLC4541] = { + .channels = tlc4541_channels, + .num_channels = ARRAY_SIZE(tlc4541_channels), + }, + [TLC3541] = { + .channels = tlc3541_channels, + .num_channels = ARRAY_SIZE(tlc3541_channels), + }, +}; + +static irqreturn_t tlc4541_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tlc4541_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf, + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int tlc4541_get_range(struct tlc4541_state *st) +{ + int vref; + + vref = regulator_get_voltage(st->reg); + if (vref < 0) + return vref; + + vref /= 1000; + + return vref; +} + +static int tlc4541_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret = 0; + struct tlc4541_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = spi_sync(st->spi, &st->scan_single_msg); + iio_device_release_direct_mode(indio_dev); + if (ret < 0) + return ret; + *val = be16_to_cpu(st->rx_buf[0]); + *val = *val >> chan->scan_type.shift; + *val &= GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = tlc4541_get_range(st); + if (ret < 0) + return ret; + *val = ret; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static const struct iio_info tlc4541_info = { + .read_raw = &tlc4541_read_raw, + .driver_module = THIS_MODULE, +}; + +static int tlc4541_probe(struct spi_device *spi) +{ + struct tlc4541_state *st; + struct iio_dev *indio_dev; + const struct tlc4541_chip_info *info; + int ret; + int8_t device_init = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + info = &tlc4541_chip_info[spi_get_device_id(spi)->driver_data]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + indio_dev->info = &tlc4541_info; + + /* perform reset */ + spi_write(spi, &device_init, 1); + + /* Setup default message */ + st->scan_single_xfer[0].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[0].len = 3; + st->scan_single_xfer[1].delay_usecs = 3; + st->scan_single_xfer[2].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[2].len = 2; + + spi_message_init(&st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[0], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[1], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[2], &st->scan_single_msg); + + st->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &tlc4541_trigger_handler, NULL); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer; + + return 0; + +error_cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + regulator_disable(st->reg); + + return ret; +} + +static int tlc4541_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct tlc4541_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(st->reg); + + return 0; +} + +#ifdef CONFIG_OF + +static const struct of_device_id tlc4541_dt_ids[] = { + { .compatible = "ti,tlc3541", }, + { .compatible = "ti,tlc4541", }, + {} +}; +MODULE_DEVICE_TABLE(of, tlc4541_dt_ids); + +#endif + +static const struct spi_device_id tlc4541_id[] = { + {"tlc3541", TLC3541}, + {"tlc4541", TLC4541}, + {} +}; +MODULE_DEVICE_TABLE(spi, tlc4541_id); + +static struct spi_driver tlc4541_driver = { + .driver = { + .name = "tlc4541", + .of_match_table = of_match_ptr(tlc4541_dt_ids), + }, + .probe = tlc4541_probe, + .remove = tlc4541_remove, + .id_table = tlc4541_id, +}; +module_spi_driver(tlc4541_driver); + +MODULE_AUTHOR("Phil Reid "); +MODULE_DESCRIPTION("Texas Instruments TLC4541 ADC"); +MODULE_LICENSE("GPL v2");