Hi, On Wed, 31 Jul 2013 08:54:54 +0200 Lars-Peter Clausen <lars@xxxxxxxxxx> wrote: > On 07/31/2013 12:46 AM, Otavio Salvador wrote: >> On Fri, Jul 26, 2013 at 6:10 PM, Mario Domenech Goulart >> <mario@xxxxxxxxxxxxxxxx> wrote: >>> Hi, >>> >>> We are working on a driver for TI ADS124x ADC series (ADS1246, >>> ADS1247 and ADS1248. Datasheet: http://www.ti.com/litv/pdf/sbas426g) >>> >>> The attached patch is what we have so far. It is by no means >>> finished. We are actually submitting it in the hope you can >>> review it and provide feedback. >>> >>> Some observations and questions in advance: >>> >>> * we've set the chip to only convert on-demand. I.e., it is not >>> constantly converting. Conversions are only performed when >>> requested via sysfs. We are not sure about the best approach >>> with regard to that behavior. Should it be constantly >>> converting? >>> >>> * we've set the device as IIO_TEMP, although what is currently >>> exposed in sysfs is voltage. The chip is targeted to >>> temperature sensors, but the actual output of the ADC is >>> voltage, so we don't know exactly what to use as type. >>> >>> * we are aware of some ugly hacks like wait_for_drdy. :-) What's >>> the best approach to wait for the data ready signal? >>> >>> * in fact we've been mostly working with ADS1247 and haven't >>> concentrated on supporting ADS1246 and ADS1248 for now, but we >>> intend to do so. >> >> Could someone give us some feedback on this? >> >> We are really looking for some initial review on this patch so we can >> clean it up and do the need changes to send it for official review >> later. >> > > Can you send the patch inline instead of as an attachment? Sure. >From a31b12f59cc51d414a1e56404707c1d5d227e2f2 Mon Sep 17 00:00:00 2001 From: Mario Domenech Goulart <mario@xxxxxxxxxxxxxxxx> Date: Fri, 26 Jul 2013 16:27:08 -0300 Subject: [PATCH] Initial (and incomplete) support for TI ADS124x ADC series (WIP) Signed-off-by: Mario Domenech Goulart <mario@xxxxxxxxxxxxxxxx> --- drivers/staging/iio/adc/Kconfig | 10 + drivers/staging/iio/adc/Makefile | 1 + drivers/staging/iio/adc/ti-ads124x.c | 495 ++++++++++++++++++++++++++++++++++ 3 files changed, 506 insertions(+) create mode 100644 drivers/staging/iio/adc/ti-ads124x.c diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig index cabc7a3..fec2966 100644 --- a/drivers/staging/iio/adc/Kconfig +++ b/drivers/staging/iio/adc/Kconfig @@ -130,4 +130,14 @@ config SPEAR_ADC Say yes here to build support for the integrated ADC inside the ST SPEAr SoC. Provides direct access via sysfs. +config TI_ADS124X + tristate "Texas Instruments ADS1246/7/8 temperature sensor and ADC driver" + depends on SPI + help + Say yes here to build support for Texas Instruments ADS1246/7/8 temperature + sensor and ADC driver. + + To compile this driver as a module, choose M here: the + module will be called ti-ads124x + endmenu diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile index 3e9fb14..3badf8c 100644 --- a/drivers/staging/iio/adc/Makefile +++ b/drivers/staging/iio/adc/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_AD7280) += ad7280a.o obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o obj-$(CONFIG_SPEAR_ADC) += spear_adc.o +obj-$(CONFIG_TI_ADS124X) += ti-ads124x.o diff --git a/drivers/staging/iio/adc/ti-ads124x.c b/drivers/staging/iio/adc/ti-ads124x.c new file mode 100644 index 0000000..13467ca --- /dev/null +++ b/drivers/staging/iio/adc/ti-ads124x.c @@ -0,0 +1,495 @@ +/* + * Texas Instruments ADS1246/7/8 ADC driver + * + * Copyright (c) 2013 O.S. Systems Software LTDA. + * Copyright (c) 2013 Otavio Salvador <otavio@xxxxxxxxxxxxxxxx> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> + +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/delay.h> + +/* Register addresses for ADS1247 and ADS1248 */ +#define ADS124X_REG_MUX0 0x00 +#define ADS124X_REG_VBIAS 0x01 +#define ADS124X_REG_MUX1 0x02 +#define ADS124X_REG_SYS0 0x03 +#define ADS124X_REG_OFC0 0x04 +#define ADS124X_REG_OFC1 0x05 +#define ADS124X_REG_OFC2 0x06 +#define ADS124X_REG_FSC0 0x07 +#define ADS124X_REG_FSC1 0x08 +#define ADS124X_REG_FSC2 0x09 +#define ADS124X_REG_IDAC0 0x0a +#define ADS124X_REG_IDAC1 0x0b +#define ADS124X_REG_GPIOCFG 0x0c +#define ADS124X_REG_GPIODIR 0x0d +#define ADS124X_REG_GPIODAT 0x0e + +/* SPI Commands */ +#define ADS124X_SPI_WAKEUP 0x00 +#define ADS124X_SPI_SLEEP 0x02 +#define ADS124X_SPI_SYNC1 0x04 +#define ADS124X_SPI_SYNC2 0x04 +#define ADS124X_SPI_RESET 0x06 +#define ADS124X_SPI_NOP 0xff +#define ADS124X_SPI_RDATA 0x12 +#define ADS124X_SPI_RDATAC 0x14 +#define ADS124X_SPI_SDATAC 0x16 +#define ADS124X_SPI_RREG 0x20 +#define ADS124X_SPI_WREG 0x40 +#define ADS124X_SPI_SYSOCAL 0x60 +#define ADS124X_SPI_SYSGCAL 0x61 +#define ADS124X_SPI_SELFOCAL 0x62 + +#define ADS124X_SINGLE_REG 0x00 + +static const u16 ads124x_sample_freq_avail[] = { 5, 10, 20, 40, 80, 160, + 320, 640, 1000, 2000 +}; + +static const u8 ads124x_sample_gain_avail[] = { 1, 2, 4, 8, 16, 32, 64, 128 }; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("5 10 20 40 80 160 320 640 1000 2000"); + +static IIO_CONST_ATTR(sampling_gain_available, "1 2 4 8 16 32 64 128"); + +static struct attribute *ads124x_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_sampling_gain_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ads124x_attribute_group = { + .attrs = ads124x_attributes, +}; + +struct ads124x_state { + struct spi_device *spi; + int drdy_gpio; + int start_gpio; + int reset_gpio; + u32 vref_mv; + int sample_rate; + + struct mutex lock; +}; + +static const struct of_device_id ads124x_ids[] = { + {.compatible = "ti,ads1247"}, + {} +}; + +MODULE_DEVICE_TABLE(of, ads124x_ids); + +static int ads124x_stop_reading_continuously(struct ads124x_state *st) +{ + u8 cmd[1]; + int ret; + cmd[0] = ADS124X_SPI_SDATAC; + + ret = spi_write(st->spi, cmd, 1); + + return ret; +} + +static int ads124x_read_reg(struct ads124x_state *st, u8 reg, u8 *buf) +{ + u8 read_cmd[2]; + int ret; + + read_cmd[0] = ADS124X_SPI_RREG | reg; + read_cmd[1] = ADS124X_SINGLE_REG; + spi_write(st->spi, read_cmd, 2); + + ret = spi_read(st->spi, buf, 1); + + return ret; +} + +static int ads124x_write_reg(struct ads124x_state *st, + u8 reg, u8 *buf, size_t len) +{ + u8 write_cmd[3]; + int ret; + + write_cmd[0] = ADS124X_SPI_WREG | reg; + write_cmd[1] = ADS124X_SINGLE_REG; + write_cmd[2] = *buf; + + ret = spi_write(st->spi, write_cmd, 3); + + return ret; +} + +static u32 ads124x_sample_to_32bit(u8 *sample) +{ + int sample32 = 0; + sample32 = sample[0] << 16; + sample32 |= sample[1] << 8; + sample32 |= sample[2]; + return sign_extend32(sample32, 23); +} + +static void wait_for_drdy(int drdy_gpio) +{ + u8 drdy; + + for (;;) { + drdy = gpio_get_value(drdy_gpio); + if (!drdy) + return; + usleep_range(1000, 2000); + } +} + +static int ads124x_convert(struct ads124x_state *st) +{ + u8 cmd[1], res[3]; + u32 res32; + int ret; + cmd[0] = ADS124X_SPI_RDATA; + + ret = spi_write(st->spi, cmd, 1); + + /* Wait for conversion results */ + wait_for_drdy(st->drdy_gpio); + + ret = spi_read(st->spi, res, 3); + + res32 = ads124x_sample_to_32bit(res); + + return res32; +} + +static void ads124x_start(struct ads124x_state *st) +{ + gpio_set_value(st->start_gpio, 1); + /* FIXME: the sleep time is not accurate: see the datasheet, */ + /* table 15 at page 33. */ + msleep(200); + return; +} + +static void ads124x_reset(struct ads124x_state *st) +{ + u8 cmd[1]; + int ret; + + gpio_set_value(st->reset_gpio, 0); + gpio_set_value(st->reset_gpio, 1); + + cmd[0] = ADS124X_SPI_RESET; + ret = spi_write(st->spi, cmd, 1); + + msleep(200); /* FIXME: that's arbitrary. */ + + return; +} + +static int ads124x_select_input(struct ads124x_state *st, + struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + u8 mux0; + int ret; + + mutex_lock(&st->lock); + + ret = ads124x_read_reg(st, ADS124X_REG_MUX0, &mux0); + + if (ret < 0) + goto release_lock_and_return; + + /* Preserve the two most significant bits */ + mux0 &= 0xc0; + + /* Select positive and negative inputs */ + mux0 |= (chan->channel << 3) | chan->channel2; + + ret = ads124x_write_reg(st, ADS124X_REG_MUX0, &mux0, 1); + +release_lock_and_return: + mutex_unlock(&st->lock); + return ret; +} + +static int ads124x_set_pga_gain(struct ads124x_state *st, u8 gain) +{ + int ret; + u8 sys0; + + mutex_lock(&st->lock); + + ret = ads124x_read_reg(st, ADS124X_REG_SYS0, &sys0); + + if (ret < 0) + goto release_lock_and_return; + + sys0 = (sys0 & 0x8f) | (gain << 4); + + ret = ads124x_write_reg(st, ADS124X_REG_SYS0, &sys0, 1); + +release_lock_and_return: + mutex_unlock(&st->lock); + return ret; +} + +static int ads124x_set_sample_rate(struct ads124x_state *st) +{ + u8 sys0; + int ret; + ret = ads124x_read_reg(st, ADS124X_REG_SYS0, &sys0); + if (ret < 0) + return ret; + + sys0 |= 0x0f & st->sample_rate; + + ret = ads124x_write_reg(st, ADS124X_REG_SYS0, &sys0, 1); + + return ret; +} + +static int ads124x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ads124x_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ads124x_select_input(st, indio_dev, chan); + wait_for_drdy(st->drdy_gpio); + ret = ads124x_convert(st); + *val = ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = (1 << 23) - 1; + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = ads124x_sample_freq_avail[st->sample_rate]; + *val2 = 0; + return IIO_VAL_INT; + + default: + break; + } + + return -EINVAL; +} + +static int ads124x_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ads124x_state *st = iio_priv(indio_dev); + int ret; + u8 i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < 8; i++) /* 8 possible values for PGA gain */ + if (val == ads124x_sample_gain_avail[i]) + return ads124x_set_pga_gain(st, i); + break; + + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < ARRAY_SIZE(ads124x_sample_freq_avail); i++) + if (val == ads124x_sample_freq_avail[i]) { + mutex_lock(&st->lock); + st->sample_rate = i; + ret = ads124x_set_sample_rate(st); + mutex_unlock(&st->lock); + return ret; + } + break; + + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info ads124x_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = &ads124x_read_raw, + .write_raw = &ads124x_write_raw, + .attrs = &ads124x_attribute_group, +}; + +static int ads124x_init_chan_array(struct iio_dev *indio_dev, + struct device_node *np) +{ + struct iio_chan_spec *chan_array; + int num_inputs = indio_dev->num_channels * 2; + int *channels_config; + int i, ret; + + channels_config = kcalloc(num_inputs, sizeof(u32), GFP_KERNEL); + + ret = of_property_read_u32_array(np, "channels", + channels_config, num_inputs); + if (ret < 0) + return ret; + + chan_array = kcalloc(indio_dev->num_channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + + if (chan_array == NULL) + return -ENOMEM; + + for (i = 0; i < num_inputs; i += 2) { + struct iio_chan_spec *chan = chan_array + (i / 2); + chan->type = IIO_TEMP; + chan->indexed = 1; + chan->channel = channels_config[i]; + chan->channel2 = channels_config[i + 1]; + chan->differential = 1; + chan->scan_index = i; + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ); + } + + indio_dev->channels = chan_array; + + return indio_dev->num_channels; +} + +static int ads124x_probe(struct spi_device *spi) +{ + struct device_node *np = spi->dev.of_node; + struct iio_dev *indio_dev; + struct ads124x_state *st; + int ret; + + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + /* Initialize GPIO pins */ + st->drdy_gpio = of_get_named_gpio(np, "drdy-gpio", 0); + st->start_gpio = of_get_named_gpio(np, "start-gpio", 0); + st->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); + + ret = devm_gpio_request_one(&indio_dev->dev, st->drdy_gpio, + GPIOF_IN, "adc-drdy"); + if (ret) { + dev_err(&indio_dev->dev, "failed to get adc-drdy-gpios: %d\n", + ret); + goto error; + } + + ret = devm_gpio_request_one(&indio_dev->dev, st->start_gpio, + GPIOF_OUT_INIT_LOW, "adc-start"); + if (ret) { + dev_err(&indio_dev->dev, "failed to get adc-start-gpios: %d\n", + ret); + goto error; + } + + ret = devm_gpio_request_one(&indio_dev->dev, st->reset_gpio, + GPIOF_OUT_INIT_LOW, "adc-reset"); + if (ret) { + dev_err(&indio_dev->dev, "failed to get adc-reset-gpios: %d\n", + ret); + goto error; + } + + ret = of_property_read_u32(np, "vref-mv", &st->vref_mv); + if (ret < 0) + goto error; + + /* Initialize SPI */ + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + st->spi->mode = SPI_MODE_1; + st->spi->bits_per_word = 8; + ret = spi_setup(spi); + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = np->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads124x_iio_info; + + /* Setup the ADC channels available on the board */ + ret = of_property_read_u32(np, "#channels", &indio_dev->num_channels); + if (ret < 0) + goto error; + + ret = ads124x_init_chan_array(indio_dev, np); + if (ret < 0) + goto error; + + ret = iio_device_register(indio_dev); + if (ret) + goto error; + + ads124x_reset(st); + ads124x_start(st); + ads124x_stop_reading_continuously(st); + + mutex_init(&st->lock); + + return 0; + +error: + iio_device_free(indio_dev); + dev_err(&spi->dev, "ADS124x: Error while probing.\n"); + + return ret; +} + +static int ads124x_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ads124x_state *st; + + iio_device_unregister(indio_dev); + iio_device_free(indio_dev); + + st = iio_priv(indio_dev); + mutex_destroy(&st->lock); + + return 0; +} + +static struct spi_driver ads124x_driver = { + .driver = { + .name = "ti-ads124x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ads124x_ids), + }, + .probe = ads124x_probe, + .remove = ads124x_remove, +}; + +module_spi_driver(ads124x_driver); + +MODULE_AUTHOR("Otavio Salvador <otavio@xxxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Texas Instruments ADS1246/7/8 driver"); +MODULE_LICENSE("GPL v2"); -- 1.7.10.4 Best wishes. Mario -- http://www.ossystems.com.br -- 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