nuttx/drivers/sensors/mlx90614.c
Alin Jerpelea 5e6d58dd03 drivers: Alan Carvalho de Assis: update licenses to Apache
Alan Carvalho de Assis 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-03-13 05:56:43 -08:00

474 lines
13 KiB
C

/****************************************************************************
* drivers/sensors/mlx90614.c
*
* 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.
*
****************************************************************************/
/* Character driver for the Melexis MLX90614 Infrared Thermometer */
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <errno.h>
#include <debug.h>
#include <stdlib.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/signal.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/sensors/ioctl.h>
#include <nuttx/sensors/mlx90614.h>
#include <nuttx/random.h>
#ifdef CONFIG_MLX90614_CRC
#include <crc8.h>
#endif
#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_MLX90614)
/****************************************************************************
* Pre-process Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
struct mlx90614_dev_s
{
FAR struct i2c_master_s *i2c; /* I2C interface */
uint8_t addr; /* I2C address */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* I2C Helpers */
static int mlx90614_read_word(FAR struct mlx90614_dev_s *priv,
uint8_t cmd, FAR uint16_t *regval);
static int mlx90614_write_word(FAR struct mlx90614_dev_s *priv,
uint8_t cmd, uint16_t regval);
/* Character driver methods */
static int mlx90614_open(FAR struct file *filep);
static int mlx90614_close(FAR struct file *filep);
static ssize_t mlx90614_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t mlx90614_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen);
static int mlx90614_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_mlx90614_fops =
{
mlx90614_open, /* open */
mlx90614_close, /* close */
mlx90614_read, /* read */
mlx90614_write, /* write */
NULL, /* seek */
mlx90614_ioctl, /* ioctl */
NULL /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
, NULL /* unlink */
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: mlx90614_read_word
*
* Description:
* Read 16-bit register
*
****************************************************************************/
static int mlx90614_read_word(FAR struct mlx90614_dev_s *priv, uint8_t cmd,
FAR uint16_t *regval)
{
struct i2c_config_s config;
#ifdef CONFIG_MLX90614_CRC
uint8_t checkcrc[6];
uint8_t *buffer;
#else
uint8_t buffer[3];
#endif
uint8_t crc;
int ret;
#ifdef CONFIG_MLX90614_CRC
/* Recreate the I2C byte sequence to check the CRC PEC field */
checkcrc[0] = I2C_WRITEADDR8(priv->addr);
checkcrc[1] = cmd;
checkcrc[2] = I2C_READADDR8(priv->addr);
/* Point "buffer" to checkcrc[3] to fill it with received bytes */
buffer = (uint8_t *) &checkcrc[3];
#endif
/* Set up the I2C configuration */
config.frequency = 100000;
config.address = priv->addr;
config.addrlen = 7;
/* Write the Command and read 16-bits + PEC from the device */
ret = i2c_writeread(priv->i2c, &config, (FAR const uint8_t *) &cmd, 1,
buffer, 3);
if (ret < 0)
{
snerr ("i2c_writeread failed: %d\n", ret);
return ret;
}
/* Copy the content of the buffer to the location of the uint16_t pointer */
*regval = (uint16_t)(buffer[0] | (buffer[1] << 8));
sninfo("value[0]: %02x | value[1]: %02x | value[2]: %02x | ret: %d\n",
buffer[0], buffer[1], buffer[2], ret);
#ifdef CONFIG_MLX90614_CRC
crc = crc8ccitt(checkcrc, 5);
sninfo("Calculated CRC: 0x%02X\n", crc);
if (crc != buffer[2])
{
snerr("Invalid CRC! Expected: 0x%02X read: 0x%02X\n", crc, buffer[2]);
return -EFAULT;
}
#endif
return OK;
}
/****************************************************************************
* Name: mlx90614_write_word
*
* Description:
* Write to a 16-bit register
*
****************************************************************************/
static int mlx90614_write_word(FAR struct mlx90614_dev_s *priv, uint8_t cmd,
uint16_t regval)
{
struct i2c_config_s config;
uint8_t pkgcrc[5];
uint8_t *buffer;
uint8_t crc;
int ret;
/* Set up the I2C configuration */
config.frequency = 100000;
config.address = priv->addr;
config.addrlen = 7;
/* First write 0x00 0x00 to erase the EEPROM */
pkgcrc[0] = I2C_WRITEADDR8(priv->addr);
pkgcrc[1] = cmd;
pkgcrc[2] = 0x00;
pkgcrc[3] = 0x00;
/* Generate the CRC to put in the PEC field */
crc = crc8ccitt(pkgcrc, 4);
sninfo("Generated CRC: 0x%02X\n", crc);
pkgcrc[4] = crc;
buffer = &(pkgcrc[1]);
/* Write the Command + 2 Bytes + PEC to device */
ret = i2c_write(priv->i2c, &config, (FAR const uint8_t *) buffer, 4);
if (ret < 0)
{
snerr ("i2c_writeread failed: %d\n", ret);
return ret;
}
/* Wait the EEPROM erase */
nxsig_usleep(10 * 1000);
/* Create the I2C command that will be sent to device */
pkgcrc[0] = I2C_WRITEADDR8(priv->addr);
pkgcrc[1] = cmd;
pkgcrc[2] = (regval & 0xff);
pkgcrc[3] = (regval & 0xff00) >> 8;
/* Generate the CRC to put in the PEC field */
crc = crc8ccitt(pkgcrc, 4);
sninfo("Generated CRC: 0x%02X\n", crc);
pkgcrc[4] = crc;
/* Write the Command + 2 Bytes + PEC to device */
ret = i2c_write(priv->i2c, &config, (FAR const uint8_t *) buffer, 4);
if (ret < 0)
{
snerr ("i2c_writeread failed: %d\n", ret);
return ret;
}
sninfo("New address 0x%02x stored correctly!\n", regval);
return OK;
}
/****************************************************************************
* Name: mlx90614_open
*
* Description:
* This function is called whenever the MLX90614 device is opened.
*
****************************************************************************/
static int mlx90614_open(FAR struct file *filep)
{
return OK;
}
/****************************************************************************
* Name: mlx90614_close
*
* Description:
* This routine is called when the MLX90614 device is closed.
*
****************************************************************************/
static int mlx90614_close(FAR struct file *filep)
{
return OK;
}
/****************************************************************************
* Name: mlx90614_read
****************************************************************************/
static ssize_t mlx90614_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct inode *inode;
FAR struct mlx90614_dev_s *priv;
FAR struct mlx90614_temp_s *temp = (FAR struct mlx90614_temp_s *)buffer;
uint8_t cmd;
int ret;
DEBUGASSERT(filep);
inode = filep->f_inode;
DEBUGASSERT(inode && inode->i_private);
priv = (FAR struct mlx90614_dev_s *)inode->i_private;
/* Check if the user is reading the right size */
if (buflen != sizeof(struct mlx90614_temp_s))
{
snerr("ERROR: You need to read 2 bytes from this sensor!\n");
return -EINVAL;
}
/* Read Ambient Temperature sensor */
cmd = MLX90614_CMD_RAM_ACCESS | MLX90614_TA;
ret = mlx90614_read_word(priv, cmd, &(temp->ta));
if (ret < 0)
{
snerr("ERROR: Error reading thermometer sensor!\n");
return ret;
}
/* Read Object1 Temperature sensor */
cmd = MLX90614_CMD_RAM_ACCESS | MLX90614_TOBJ1;
ret = mlx90614_read_word(priv, cmd, &(temp->tobj1));
if (ret < 0)
{
snerr("ERROR: Error reading thermometer sensor!\n");
return ret;
}
/* Read Object2 Temperature sensor - some sensor doesn't have it */
cmd = MLX90614_CMD_RAM_ACCESS | MLX90614_TOBJ2;
ret = mlx90614_read_word(priv, cmd, &(temp->tobj2));
if (ret < 0)
{
snerr("ERROR: Error reading thermometer sensor!\n");
return ret;
}
return buflen;
}
/****************************************************************************
* Name: mlx90614_write
****************************************************************************/
static ssize_t mlx90614_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen)
{
return -ENOSYS;
}
/****************************************************************************
* Name: mlx90614_ioctl
****************************************************************************/
static int mlx90614_ioctl(FAR struct file *filep, int cmd,
unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct mlx90614_dev_s *priv = inode->i_private;
int ret = OK;
switch (cmd)
{
/* Change the device to use a new i2c/smbus address */
case SNIOC_CHANGE_SMBUSADDR:
{
FAR struct mlx90614_dev_s newdev;
FAR uint8_t *ptr = (FAR uint8_t *)((uintptr_t)arg);
uint16_t newaddr;
uint8_t smbcmd;
/* EEPROM reflash only happen when sent at I2C address 0x00 */
newdev.i2c = priv->i2c;
newdev.addr = 0x00;
/* The I2C address is put in the high byte position */
newaddr = (uint16_t) *ptr;
sninfo("Setting new address: 0x%02x\n", newaddr);
smbcmd = MLX90614_CMD_EEPROM_ACCESS | MLX90614_SMBUS_ADDR;
ret = mlx90614_write_word(&newdev, smbcmd, newaddr);
if (ret < 0)
{
snerr("ERROR: Failed to change the I2C/SMBus address!\n");
}
}
break;
default:
snerr("ERROR: Unrecognized cmd: %d\n", cmd);
ret = -ENOTTY;
break;
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: mlx90614_register
*
* Description:
* Register the MLX90614 character device as 'devpath'
*
* Input Parameters:
* devpath - The full path to the driver to register. E.g., "/dev/therm0"
* i2c - An instance of the I2C interface to use to communicate with
* MLX90614
* addr - The I2C address of the MLX90614.
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int mlx90614_register(FAR const char *devpath, FAR struct i2c_master_s *i2c,
uint8_t addr)
{
int ret;
/* Sanity check */
DEBUGASSERT(i2c != NULL);
/* Initialize the MLX90614 device structure */
FAR struct mlx90614_dev_s *priv =
(FAR struct mlx90614_dev_s *)kmm_malloc(sizeof(struct mlx90614_dev_s));
if (priv == NULL)
{
snerr("ERROR: Failed to allocate instance\n");
return -ENOMEM;
}
priv->i2c = i2c;
priv->addr = addr;
/* Register the character driver */
ret = register_driver(devpath, &g_mlx90614_fops, 0666, priv);
if (ret < 0)
{
snerr("ERROR: Failed to register driver: %d\n", ret);
kmm_free(priv);
}
return ret;
}
#endif /* CONFIG_I2C && CONFIG_SENSORS_MLX90614 */