From 91f96b6ecbf1396c47954664c5d5bb9ce5c185e1 Mon Sep 17 00:00:00 2001 From: Martin Lederhilger Date: Tue, 28 Mar 2017 06:34:37 -0600 Subject: [PATCH] drivers/analog: Add driver for the LTC1767L ADC. --- drivers/analog/Kconfig | 19 ++ drivers/analog/Make.defs | 4 + drivers/analog/ltc1867l.c | 380 ++++++++++++++++++++++++++++++++ include/nuttx/analog/ltc1867l.h | 137 ++++++++++++ 4 files changed, 540 insertions(+) create mode 100644 drivers/analog/ltc1867l.c create mode 100644 include/nuttx/analog/ltc1867l.h diff --git a/drivers/analog/Kconfig b/drivers/analog/Kconfig index c9a2334361..ec14bdcd05 100644 --- a/drivers/analog/Kconfig +++ b/drivers/analog/Kconfig @@ -66,6 +66,25 @@ config ADS1255_FREQUENCY endif # ADC_ADS125X +config ADC_LTC1867L + bool "LTC 1863L/1867L support" + default n + select SPI + ---help--- + Enable driver support for the LTC 1863L (12 bit) and LTC 1867L (16 bit) SPI powered ADC. + + Note that the ADC conversion is started via the ANIOC_TRIGGER iotcl. + +if ADC_LTC1867L + +config LTC1867L_FREQUENCY + int "LTC 1863L/1867L SPI frequency" + default 1000000 + ---help--- + LTC 1863L/1867L SPI frequency. Maximum is 20 MHz. + +endif # ADC_LTC1867L + config ADC_PGA11X bool "TI PGA112/3/6/7 support" default n diff --git a/drivers/analog/Make.defs b/drivers/analog/Make.defs index fbcc369a4c..01d5a2fe8e 100644 --- a/drivers/analog/Make.defs +++ b/drivers/analog/Make.defs @@ -83,6 +83,10 @@ endif ifeq ($(CONFIG_ADC_ADS125X),y) CSRCS += ads1255.c endif + +ifeq ($(CONFIG_ADC_LTC1867L),y) + CSRCS += ltc1867l.c +endif endif # Add analog driver build support (the nested if-then-else implements an OR) diff --git a/drivers/analog/ltc1867l.c b/drivers/analog/ltc1867l.c new file mode 100644 index 0000000000..0f5bf1a758 --- /dev/null +++ b/drivers/analog/ltc1867l.c @@ -0,0 +1,380 @@ +/**************************************************************************** + * arch/drivers/analog/ltc1867l.c + * + * Copyright (C) 2017 DS-Automotion GmbH. All rights reserved. + * Author: Martin Lederhilger + * + * This file is a part of NuttX: + * + * Copyright (C) 2017 Gregory Nutt. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if defined(CONFIG_ADC_LTC1867L) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Device uses SPI Mode 1: CKPOL = 0, CKPHA = 0 */ + +#define LTC1867L_SPI_MODE (SPIDEV_MODE0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct ltc1867l_dev_s +{ + FAR const struct adc_callback_s *cb; + FAR struct spi_dev_s *spi; + unsigned int devno; + FAR struct ltc1867l_channel_config_s *channel_config; + int channel_config_count; + sem_t sem; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void adc_lock(FAR struct spi_dev_s *spi); +static void adc_unlock(FAR struct spi_dev_s *spi); + +/* ADC methods */ + +static int adc_bind(FAR struct adc_dev_s *dev, + FAR const struct adc_callback_s *callback); +static void adc_reset(FAR struct adc_dev_s *dev); +static int adc_setup(FAR struct adc_dev_s *dev); +static void adc_shutdown(FAR struct adc_dev_s *dev); +static void adc_rxint(FAR struct adc_dev_s *dev, bool enable); +static int adc_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct adc_ops_s g_adcops = +{ + .ao_bind = adc_bind, /* ao_bind */ + .ao_reset = adc_reset, /* ao_reset */ + .ao_setup = adc_setup, /* ao_setup */ + .ao_shutdown = adc_shutdown, /* ao_shutdown */ + .ao_rxint = adc_rxint, /* ao_rxint */ + .ao_ioctl = adc_ioctl /* ao_read */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: adc_lock + * + * Description: + * Lock and configure the SPI bus. + * + ****************************************************************************/ + +static void adc_lock(FAR struct spi_dev_s *spi) +{ + (void)SPI_LOCK(spi, true); + SPI_SETMODE(spi, LTC1867L_SPI_MODE); + SPI_SETBITS(spi, 16); + (void)SPI_HWFEATURES(spi, 0); + SPI_SETFREQUENCY(spi, CONFIG_LTC1867L_FREQUENCY); +} + +/**************************************************************************** + * Name: adc_unlock + * + * Description: + * Unlock the SPI bus. + * + ****************************************************************************/ + +static void adc_unlock(FAR struct spi_dev_s *spi) +{ + (void)SPI_LOCK(spi, false); +} + +/**************************************************************************** + * Name: adc_bind + * + * Description: + * Bind the upper-half driver callbacks to the lower-half implementation. This + * must be called early in order to receive ADC event notifications. + * + ****************************************************************************/ + +static int adc_bind(FAR struct adc_dev_s *dev, + FAR const struct adc_callback_s *callback) +{ + FAR struct ltc1867l_dev_s *priv = (FAR struct ltc1867l_dev_s *)dev->ad_priv; + priv->cb = callback; + return OK; +} + +/**************************************************************************** + * Name: adc_reset + * + * Description: + * Reset the ADC device. Called early to initialize the hardware. This + * is called, before ao_setup() and on error conditions. + * + ****************************************************************************/ + +static void adc_reset(FAR struct adc_dev_s *dev) +{ +} + +/**************************************************************************** + * Name: adc_setup + * + * Description: + * Configure the ADC. This method is called the first time that the ADC + * device is opened. This will occur when the port is first opened. + * This setup includes configuring and attaching ADC interrupts. Interrupts + * are all disabled upon return. + * + ****************************************************************************/ + +static int adc_setup(FAR struct adc_dev_s *dev) +{ + return OK; +} + +/**************************************************************************** + * Name: adc_shutdown + * + * Description: + * Disable the ADC. This method is called when the ADC device is closed. + * This method reverses the operation the setup method. + * + ****************************************************************************/ + +static void adc_shutdown(FAR struct adc_dev_s *dev) +{ +} + +/**************************************************************************** + * Name: adc_rxint + * + * Description: + * Call to enable or disable RX interrupts + * + ****************************************************************************/ + +static void adc_rxint(FAR struct adc_dev_s *dev, bool enable) +{ +} + +/**************************************************************************** + * Name: adc_ioctl + * + * Description: + * All ioctl calls will be routed through this method + * + ****************************************************************************/ + +static int adc_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct ltc1867l_dev_s *priv = (FAR struct ltc1867l_dev_s *)dev->ad_priv; + FAR struct spi_dev_s *spi = priv->spi; + int i; + uint16_t command; + uint16_t data; + int32_t dataToPost; + int ret = OK; + + if(cmd == ANIOC_TRIGGER) + { + while (sem_wait(&priv->sem) != OK); + + adc_lock(spi); + + for (i = 0; i <= priv->channel_config_count; i++) + { + SPI_SELECT(spi, priv->devno, true); + + if (i < priv->channel_config_count) + { + command = priv->channel_config[i].analog_multiplexer_config | + priv->channel_config[i].analog_inputMode; + command = command << 8; + } + else + { + command = 0; + } + + data = SPI_SEND(spi, command); + up_udelay(2); + SPI_SELECT(spi, priv->devno, false); + up_udelay(4); + + if (i > 0) + { + if (priv->channel_config[i-1].analog_inputMode == LTC1867L_UNIPOLAR || + (priv->channel_config[i-1].analog_inputMode == LTC1867L_BIPOLAR && + data >= 0 && data <= 0x7fff)) + { + dataToPost = data; + } + else + { + dataToPost = -(0xffff - data) - 1; + } + + priv->cb->au_receive(dev, priv->channel_config[i-1].channel, dataToPost); + } + } + + adc_unlock(spi); + sem_post(&priv->sem); + } + else + { + aerr("ERROR: Unrecognized cmd: %d\n", cmd); + ret = -ENOTTY; + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ltc1867l_register + * + * Description: + * Register the LTC1867L character device as 'devpath' + * + * Input Parameters: + * devpath - The full path to the driver to register. E.g., "/dev/adc0" + * spi - An instance of the SPI interface to use to communicate with + * LTC1867L + * devno - SPI device number + * channel_config - A pointer to an array which holds the configuration + * for each sampled channel. + * channel_config_count - Number of channels to sample + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int ltc1867l_register(FAR const char *devpath, FAR struct spi_dev_s *spi, + unsigned int devno, + FAR struct ltc1867l_channel_config_s* channel_config, + int channel_config_count) +{ + FAR struct ltc1867l_dev_s *adcpriv; + FAR struct adc_dev_s *adcdev; + int ret; + + /* Sanity check */ + + DEBUGASSERT(spi != NULL); + DEBUGASSERT(channel_config != NULL); + + /* Initialize the LTC1867L device structure */ + + adcpriv = (FAR struct ltc1867l_dev_s *)kmm_malloc(sizeof(struct ltc1867l_dev_s)); + if (adcpriv == NULL) + { + aerr("ERROR: Failed to allocate ltc1867l_dev_s instance\n"); + return -ENOMEM; + } + + adcpriv->spi = spi; + adcpriv->devno = devno; + adcpriv->channel_config = channel_config; + adcpriv->channel_config_count = channel_config_count; + + ret = sem_init(&adcpriv->sem, 1, 1); + if(ret == -1) + { + kmm_free(adcpriv); + return -errno; + } + + adcdev = (FAR struct adc_dev_s *)kmm_malloc(sizeof(struct adc_dev_s)); + if (adcdev == NULL) + { + aerr("ERROR: Failed to allocate adc_dev_s instance\n"); + sem_destroy(&adcpriv->sem); + kmm_free(adcpriv); + return -ENOMEM; + } + + memset(adcdev, 0, sizeof(struct adc_dev_s)); + adcdev->ad_ops = &g_adcops; + adcdev->ad_priv = adcpriv; + + /* Register the character driver */ + + ret = adc_register(devpath, adcdev); + if (ret < 0) + { + aerr("ERROR: Failed to register adc driver: %d\n", ret); + kmm_free(adcdev); + sem_destroy(&adcpriv->sem); + kmm_free(adcpriv); + } + + return ret; +} +#endif diff --git a/include/nuttx/analog/ltc1867l.h b/include/nuttx/analog/ltc1867l.h new file mode 100644 index 0000000000..447ba494b1 --- /dev/null +++ b/include/nuttx/analog/ltc1867l.h @@ -0,0 +1,137 @@ +/**************************************************************************** + * include/nuttx/sensors/ltc1867l.h + * + * Copyright (C) 2017 DS-Automotion GmbH. All rights reserved. + * Author: Martin Lederhilger + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_ANALOG_LTC1867L_H +#define __INCLUDE_NUTTX_ANALOG_LTC1867L_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#if defined(CONFIG_ADC_LTC1867L) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* LTC1867L Configuration *******************************************/ + +#define LTC1867L_CONFIG_BIT_SLP (1 << 1) +#define LTC1867L_CONFIG_BIT_UNI (1 << 2) +#define LTC1867L_CONFIG_BIT_COM (1 << 3) +#define LTC1867L_CONFIG_BIT_S0 (1 << 4) +#define LTC1867L_CONFIG_BIT_S1 (1 << 5) +#define LTC1867L_CONFIG_BIT_OS (1 << 6) +#define LTC1867L_CONFIG_BIT_SD (1 << 7) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum ltc1867l_analog_multiplexer_config_e +{ + LTC1867L_P_CH0_M_CH1 = 0, + LTC1867L_P_CH2_M_CH3 = LTC1867L_CONFIG_BIT_S0, + LTC1867L_P_CH4_M_CH5 = LTC1867L_CONFIG_BIT_S1, + LTC1867L_P_CH6_M_CH7 = LTC1867L_CONFIG_BIT_S1 | LTC1867L_CONFIG_BIT_S0, + LTC1867L_P_CH1_M_CH0 = LTC1867L_CONFIG_BIT_OS, + LTC1867L_P_CH3_M_CH2 = LTC1867L_CONFIG_BIT_OS | LTC1867L_CONFIG_BIT_S0, + LTC1867L_P_CH5_M_CH4 = LTC1867L_CONFIG_BIT_OS | LTC1867L_CONFIG_BIT_S1, + LTC1867L_P_CH7_M_CH6 = LTC1867L_CONFIG_BIT_OS |LTC1867L_CONFIG_BIT_S1 | LTC1867L_CONFIG_BIT_S0, + LTC1867L_P_CH0_M_GND = LTC1867L_CONFIG_BIT_SD, + LTC1867L_P_CH2_M_GND = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_S0, + LTC1867L_P_CH4_M_GND = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_S1, + LTC1867L_P_CH6_M_GND = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_S1 | LTC1867L_CONFIG_BIT_S0, + LTC1867L_P_CH1_M_GND = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_OS, + LTC1867L_P_CH3_M_GND = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_OS | LTC1867L_CONFIG_BIT_S0, + LTC1867L_P_CH5_M_GND = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_OS | LTC1867L_CONFIG_BIT_S1, + LTC1867L_P_CH7_M_GND = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_OS | LTC1867L_CONFIG_BIT_S1 | LTC1867L_CONFIG_BIT_S0, + LTC1867L_P_CH0_M_CH7COM = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_COM, + LTC1867L_P_CH2_M_CH7COM = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_S0 | LTC1867L_CONFIG_BIT_COM, + LTC1867L_P_CH4_M_CH7COM = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_S1 | LTC1867L_CONFIG_BIT_COM, + LTC1867L_P_CH6_M_CH7COM = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_S1 | LTC1867L_CONFIG_BIT_S0 | LTC1867L_CONFIG_BIT_COM, + LTC1867L_P_CH1_M_CH7COM = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_OS | LTC1867L_CONFIG_BIT_COM, + LTC1867L_P_CH3_M_CH7COM = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_OS | LTC1867L_CONFIG_BIT_S0 | LTC1867L_CONFIG_BIT_COM, + LTC1867L_P_CH5_M_CH7COM = LTC1867L_CONFIG_BIT_SD | LTC1867L_CONFIG_BIT_OS | LTC1867L_CONFIG_BIT_S1 | LTC1867L_CONFIG_BIT_COM +}; + +enum ltc1867l_analog_input_mode_e +{ + LTC1867L_UNIPOLAR = LTC1867L_CONFIG_BIT_UNI, + LTC1867L_BIPOLAR = 0, +}; + +struct ltc1867l_channel_config_s +{ + uint8_t channel; /* This will be the channel number returned in struct adc_msg_s for a conversion */ + enum ltc1867l_analog_multiplexer_config_e analog_multiplexer_config; /* Analog multiplexer configuration */ + enum ltc1867l_analog_input_mode_e analog_inputMode; /* Analog input mode */ +} packed_struct; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: ltc1867l_register + * + * Description: + * Register the LTC1867L character device as 'devpath' + * + * Input Parameters: + * devpath - The full path to the driver to register. E.g., "/dev/adc0" + * spi - An instance of the SPI interface to use to communicate with + * LTC1867L + * devno - SPI device number + * channel_config - A pointer to an array which holds the configuration + * for each sampled channel. + * channel_config_count - Number of channels to sample + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int ltc1867l_register(FAR const char *devpath, FAR struct spi_dev_s *spi, + unsigned int devno, + FAR struct ltc1867l_channel_config_s* channel_config, + int channel_config_count); + +#endif /* CONFIG_ADC_LTC1867L */ +#endif /* __INCLUDE_NUTTX_ANALOG_LTC1867L_H */