nuttx/drivers/analog/ads1242.c
Alin Jerpelea a43ff0c93a NuttX: DS Automotion GmbH: update licenses to Apache
DS Automotion GmbH has submitted the SGA and we can migrate the licenses
 to Apache.

Gregory Nutt has submitted the SGA and we can migrate the licenses
 to Apache.

Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com>
2021-04-01 12:13:12 -05:00

613 lines
20 KiB
C

/****************************************************************************
* drivers/analog/ads1242.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdlib.h>
#include <stdbool.h>
#include <fixedmath.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/spi/spi.h>
#include <nuttx/analog/ads1242.h>
#if defined(CONFIG_SPI) && defined(CONFIG_ADC_ADS1242)
/****************************************************************************
* Private Types
****************************************************************************/
struct ads1242_dev_s
{
FAR struct spi_dev_s *spi; /* Pointer to the SPI instance */
uint32_t osc_period_us; /* Period of the oscillator attached to the ADS1242 in us */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* SPI Helpers */
static void ads1242_lock(FAR struct spi_dev_s *spi);
static void ads1242_unlock(FAR struct spi_dev_s *spi);
static void ads1242_reset(FAR struct ads1242_dev_s *dev);
static void ads1242_perform_selfgain_calibration(
FAR struct ads1242_dev_s *dev);
static void ads1242_perform_selfoffset_calibration(
FAR struct ads1242_dev_s *dev);
static void ads1242_perform_systemoffset_calibration(
FAR struct ads1242_dev_s *dev);
static void ads1242_read_conversion_result(FAR struct ads1242_dev_s *dev,
FAR uint32_t *conversion_result);
static void ads1242_write_reg(FAR struct ads1242_dev_s *dev,
uint8_t const reg_addr, uint8_t const reg_value);
static void ads1242_read_reg(FAR struct ads1242_dev_s *dev,
uint8_t const reg_addr, FAR uint8_t *reg_value);
static void ads1242_set_gain(FAR struct ads1242_dev_s *dev,
ADS1242_GAIN_SELECTION const gain_selection);
static void ads1242_set_positive_input(FAR struct ads1242_dev_s *dev,
ADS1242_POSITIVE_INPUT_SELECTION const pos_in_sel);
static void ads1242_set_negative_input(FAR struct ads1242_dev_s *dev,
ADS1242_NEGATIVE_INPUT_SELECTION const neg_in_sel);
static bool ads1242_is_data_ready(FAR struct ads1242_dev_s *dev);
#if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_INFO)
static void ads1242_print_regs(FAR struct ads1242_dev_s *dev,
char const *msg);
#endif /* CONFIG_DEBUG_FEATURES && CONFIG_DEBUG_INFO */
/* Character driver methods */
static int ads1242_open(FAR struct file *filep);
static int ads1242_close(FAR struct file *filep);
static ssize_t ads1242_read(FAR struct file *, FAR char *, size_t);
static ssize_t ads1242_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int ads1242_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_ads1242_fops =
{
ads1242_open,
ads1242_close,
ads1242_read,
ads1242_write,
NULL,
ads1242_ioctl,
NULL
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ads1242_lock
*
* Description:
* Lock and configure the SPI bus.
*
****************************************************************************/
static void ads1242_lock(FAR struct spi_dev_s *spi)
{
SPI_LOCK(spi, true);
SPI_SETMODE(spi, ADS1242_SPI_MODE);
SPI_SETBITS(spi, 8);
SPI_HWFEATURES(spi, 0);
SPI_SETFREQUENCY(spi, ADS1242_SPI_FREQUENCY);
}
/****************************************************************************
* Name: ads1242_unlock
*
* Description:
* Unlock the SPI bus.
*
****************************************************************************/
static void ads1242_unlock(FAR struct spi_dev_s *spi)
{
SPI_LOCK(spi, false);
}
/****************************************************************************
* Name: ads1242_reset
****************************************************************************/
static void ads1242_reset(FAR struct ads1242_dev_s *dev)
{
ads1242_lock(dev->spi);
SPI_SELECT(dev->spi, 0, true); /* Set nADC_SPI_CS to low which
* selects the ADS1242 */
SPI_SEND(dev->spi, ADS1242_CMD_RESET); /* Issue reset command */
SPI_SELECT(dev->spi, 0, false); /* Set nADC_SPI_CS to high which
* deselects the ADS1242 */
up_mdelay(100); /* Wait a little so the device has
* time to perform a proper reset */
ads1242_unlock(dev->spi);
}
/****************************************************************************
* Name: ads1242_perform_selfgain_calibration
****************************************************************************/
static void ads1242_perform_selfgain_calibration(FAR struct ads1242_dev_s
*dev)
{
ads1242_lock(dev->spi);
SPI_SELECT(dev->spi, 0, true);
SPI_SEND(dev->spi, ADS1242_CMD_SELF_GAIN_CALIB);
SPI_SELECT(dev->spi, 0, false);
ads1242_unlock(dev->spi);
}
/****************************************************************************
* Name: ads1242_perform_selfoffset_calibration
****************************************************************************/
static void ads1242_perform_selfoffset_calibration(FAR struct ads1242_dev_s
*dev)
{
ads1242_lock(dev->spi);
SPI_SELECT(dev->spi, 0, true);
SPI_SEND(dev->spi, ADS1242_CMD_SELF_OFFSET_CALIB);
SPI_SELECT(dev->spi, 0, false);
ads1242_unlock(dev->spi);
}
/****************************************************************************
* Name: ads1242_perform_systemoffset_calibration
****************************************************************************/
static void
ads1242_perform_systemoffset_calibration(FAR struct ads1242_dev_s *dev)
{
ads1242_lock(dev->spi);
SPI_SELECT(dev->spi, 0, true);
SPI_SEND(dev->spi, ADS1242_CMD_SYSTEM_OFFSET_CALIB);
SPI_SELECT(dev->spi, 0, false);
ads1242_unlock(dev->spi);
}
/****************************************************************************
* Name: ads1242_read_conversion_result
****************************************************************************/
static void ads1242_read_conversion_result(FAR struct ads1242_dev_s *dev,
FAR uint32_t *conversion_result)
{
ads1242_lock(dev->spi);
SPI_SELECT(dev->spi, 0, true);
SPI_SEND(dev->spi, ADS1242_CMD_READ_DATA);
/* Delay between last SCLK edge for DIN and first SCLK edge for DOUT:
* RDATA, RDATAC, RREG, WREG: Min 50 x tOSC Periods
*/
up_udelay(50 * dev->osc_period_us);
*conversion_result = 0;
/* 1st Byte = MSB
* 2nd Byte = Mid-Byte
* 3rd Byte = LSB
*/
*conversion_result |= ((uint32_t)(SPI_SEND(dev->spi, 0xff))) << 16;
*conversion_result |= ((uint32_t)(SPI_SEND(dev->spi, 0xff))) << 8;
*conversion_result |= ((uint32_t)(SPI_SEND(dev->spi, 0xff))) << 0;
SPI_SELECT(dev->spi, 0, false);
ads1242_unlock(dev->spi);
}
/****************************************************************************
* Name: ads1242_write_reg
*
* Description:
* Write to the registers starting with the register address specified as
* part of the instruction. The number of registers that will be written
* is one plus the value of the second byte.
*
****************************************************************************/
static void ads1242_write_reg(FAR struct ads1242_dev_s *dev,
uint8_t const reg_addr,
uint8_t const reg_value)
{
ads1242_lock(dev->spi);
SPI_SELECT(dev->spi, 0, true);
SPI_SEND(dev->spi, ADS1242_CMD_WRITE_REGISTER | reg_addr);
SPI_SEND(dev->spi, 0x00); /* Write 1 Byte */
SPI_SEND(dev->spi, reg_value);
SPI_SELECT(dev->spi, 0, false);
ads1242_unlock(dev->spi);
}
/****************************************************************************
* Name: ads1242_read_reg
*
* Description:
* Output the data from up to 16 registers starting with the register
* address specified as part of the instruction. The number of registers
* read will be one plus the second byte count. If the count exceeds the
* remaining registers, the addresses wrap back to the beginning.
*
****************************************************************************/
static void ads1242_read_reg(FAR struct ads1242_dev_s *dev,
uint8_t const reg_addr, FAR uint8_t *reg_value)
{
ads1242_lock(dev->spi);
SPI_SELECT(dev->spi, 0, true);
SPI_SEND(dev->spi, ADS1242_CMD_READ_REGISTER | reg_addr);
SPI_SEND(dev->spi, 0x00); /* Read 1 Byte */
/* Delay between last SCLK edge for DIN and first SCLK edge for DOUT:
* RDATA, RDATAC, RREG, WREG: Min 50 x tOSC Periods
*/
up_udelay(50 * dev->osc_period_us);
*reg_value = SPI_SEND(dev->spi, 0xff);
SPI_SELECT(dev->spi, 0, false);
ads1242_unlock(dev->spi);
}
/****************************************************************************
* Name: ads1242_set_gain
****************************************************************************/
static void ads1242_set_gain(FAR struct ads1242_dev_s *dev,
ADS1242_GAIN_SELECTION const gain_selection)
{
uint8_t setup_reg_value = 0;
ads1242_read_reg(dev, ADS1242_REG_SETUP, &setup_reg_value);
setup_reg_value &= ~(ADS1242_REG_SETUP_BIT_PGA2 |
ADS1242_REG_SETUP_BIT_PGA1 |
ADS1242_REG_SETUP_BIT_PGA0);
setup_reg_value |= (uint8_t)(gain_selection);
ads1242_write_reg(dev, ADS1242_REG_SETUP, setup_reg_value);
#if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_INFO)
ads1242_print_regs(dev, "ads1242_set_gain");
#endif
/* It is necessary to perform a offset calibration after setting the gain */
ads1242_perform_selfoffset_calibration(dev);
}
/****************************************************************************
* Name: ads1242_set_positive_input
****************************************************************************/
static void ads1242_set_positive_input(FAR struct ads1242_dev_s *dev,
ADS1242_POSITIVE_INPUT_SELECTION const pos_in_sel)
{
uint8_t mux_reg_value = 0;
ads1242_read_reg(dev, ADS1242_REG_MUX, &mux_reg_value);
mux_reg_value &= ~(ADS1242_REG_MUX_BIT_PSEL3 | ADS1242_REG_MUX_BIT_PSEL2 |
ADS1242_REG_MUX_BIT_PSEL1 | ADS1242_REG_MUX_BIT_PSEL0);
mux_reg_value |= (uint8_t)(pos_in_sel);
ads1242_write_reg(dev, ADS1242_REG_MUX, mux_reg_value);
#if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_INFO)
ads1242_print_regs(dev, "ads1242_set_positive_input");
#endif
}
/****************************************************************************
* Name: ads1242_set_negative_input
****************************************************************************/
static void ads1242_set_negative_input(FAR struct ads1242_dev_s *dev,
ADS1242_NEGATIVE_INPUT_SELECTION const neg_in_sel)
{
uint8_t mux_reg_value = 0;
ads1242_read_reg(dev, ADS1242_REG_MUX, &mux_reg_value);
mux_reg_value &= ~(ADS1242_REG_MUX_BIT_NSEL3 | ADS1242_REG_MUX_BIT_NSEL2 |
ADS1242_REG_MUX_BIT_NSEL1 | ADS1242_REG_MUX_BIT_NSEL0);
mux_reg_value |= (uint8_t)(neg_in_sel);
ads1242_write_reg(dev, ADS1242_REG_MUX, mux_reg_value);
#if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_INFO)
ads1242_print_regs(dev, "ads1242_set_negative_input");
#endif
}
/****************************************************************************
* Name: ads1242_set_negative_input
****************************************************************************/
static bool ads1242_is_data_ready(FAR struct ads1242_dev_s *dev)
{
uint8_t acr_reg_value = 0xff;
ads1242_read_reg(dev, ADS1242_REG_ACR, &acr_reg_value);
return (acr_reg_value & ADS1242_REG_ACR_BIT_NDRDY) == 0;
}
/****************************************************************************
* Name: ads1242_print_regs
****************************************************************************/
#if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_INFO)
static void ads1242_print_regs(FAR struct ads1242_dev_s *dev,
char const *msg)
{
uint8_t setup_reg_value = 0;
uint8_t mux_reg_value = 0;
uint8_t acr_reg_value = 0;
ainfo("%s\n", msg);
ads1242_read_reg(dev, ADS1242_REG_SETUP, &setup_reg_value);
ads1242_read_reg(dev, ADS1242_REG_MUX, &mux_reg_value);
ads1242_read_reg(dev, ADS1242_REG_ACR, &acr_reg_value);
ainfo("SETUP %02X\n", setup_reg_value);
ainfo("MUX %02X\n", mux_reg_value);
ainfo("ACR %02X\n", acr_reg_value);
}
#endif /* CONFIG_DEBUG_FEATURES && CONFIG_DEBUG_INFO */
/****************************************************************************
* Name: ads1242_open
****************************************************************************/
static int ads1242_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct ads1242_dev_s *priv = inode->i_private;
ads1242_reset(priv);
up_mdelay(100);
ads1242_perform_selfgain_calibration(priv);
up_mdelay(100);
/* SPEED = 1 -> fMod = fOsc / 256 (fMod = Modulator Clock Speed)
* BUFEN = 1 -> Internal input buffer enabled -> results in a very high
* impedance input for the ADC ~ 5 GOhm
*/
ads1242_write_reg(priv, ADS1242_REG_ACR,
ADS1242_REG_ACR_BIT_SPEED | ADS1242_REG_ACR_BIT_BUFEN);
ads1242_perform_selfoffset_calibration(priv);
up_mdelay(100);
#if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_INFO)
ads1242_print_regs(priv, "ads1242_open");
#endif
return OK;
}
/****************************************************************************
* Name: ads1242_close
****************************************************************************/
static int ads1242_close(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct ads1242_dev_s *priv = inode->i_private;
ads1242_reset(priv);
up_mdelay(100);
return OK;
}
/****************************************************************************
* Name: ads1242_read
****************************************************************************/
static ssize_t ads1242_read(FAR struct file *filep,
FAR char *buffer, size_t buflen)
{
return -ENOSYS;
}
/****************************************************************************
* Name: ads1242_write
****************************************************************************/
static ssize_t ads1242_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen)
{
return -ENOSYS;
}
/****************************************************************************
* Name: ads1242_ioctl
****************************************************************************/
static int ads1242_ioctl (FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct ads1242_dev_s *priv = inode->i_private;
int ret = OK;
switch (cmd)
{
/* Read the result of an analog conversion */
case ANIOC_ADS2142_READ:
{
FAR uint32_t *data = (FAR uint32_t *)((uintptr_t)arg);
ads1242_read_conversion_result(priv, data);
}
break;
/* Set the gain of the ADC */
case ANIOC_ADS2142_SET_GAIN:
{
ads1242_set_gain(priv, (ADS1242_GAIN_SELECTION)(arg));
}
break;
/* Set the positive input of the ADC */
case ANIOC_ADS2142_SET_POSITIVE_INPUT:
{
ads1242_set_positive_input(priv,
(ADS1242_POSITIVE_INPUT_SELECTION)(arg));
}
break;
/* Set the negative input of the ADC */
case ANIOC_ADS2142_SET_NEGATIVE_INPUT:
{
ads1242_set_negative_input(priv,
(ADS1242_NEGATIVE_INPUT_SELECTION)(arg));
}
break;
/* Check if data is ready to be read */
case ANIOC_ADS2142_IS_DATA_READY:
{
FAR bool *is_data_ready = (FAR bool *)((uintptr_t)arg);
*is_data_ready = ads1242_is_data_ready(priv);
}
break;
/* Perform a system offset calibration - Note: Zero input signal must
* be applied.
*/
case ANIOC_ADS2142_DO_SYSTEM_OFFSET_CALIB:
{
ads1242_perform_systemoffset_calibration(priv);
}
break;
/* Command was not recognized */
default:
_err("ERROR: Unrecognized cmd: %d\n", cmd);
ret = -ENOTTY;
break;
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ads1242_register
*
* Description:
* Register the ADS1242 character device as 'devpath'
*
* Input Parameters:
* devpath - The full path to the driver to register. E.g., "/dev/ads1242"
* spi - An instance of the SPI interface to use to communicate with
* ADS1242
* osc_freq_hz - The frequency of the ADS1242 oscillator in Hz. Required
* for calculating the minimum delay periods when accessing
* the device via SPI.
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int ads1242_register(FAR const char *devpath, FAR struct spi_dev_s *spi,
uint32_t const osc_freq_hz)
{
FAR struct ads1242_dev_s *priv;
int ret;
/* Sanity check */
DEBUGASSERT(spi != NULL);
/* Initialize the ADS1242 device structure */
priv =
(FAR struct ads1242_dev_s *)kmm_malloc(sizeof(struct ads1242_dev_s));
if (priv == NULL)
{
_err("ERROR: Failed to allocate instance\n");
return -ENOMEM;
}
priv->spi = spi;
float const osc_period_us = (1000.0 * 1000.0) / ((float)(osc_freq_hz));
priv->osc_period_us = (uint32_t)(osc_period_us);
/* Register the character driver */
ret = register_driver(devpath, &g_ads1242_fops, 0666, priv);
if (ret < 0)
{
_err("ERROR: Failed to register driver: %d\n", ret);
kmm_free(priv);
}
return ret;
}
#endif /* CONFIG_SPI && CONFIG_ADC_ADS1242 */