/**************************************************************************** * drivers/sensors/lm75.c * Character driver for the STMicro LM-75 Temperature Sensor * * Copyright (C) 2011, 2013, 2016 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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 #if defined(CONFIG_I2C) && defined(CONFIG_I2C_LM75) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef CONFIG_LM75_I2C_FREQUENCY # define CONFIG_LM75_I2C_FREQUENCY 100000 #endif /* Centigrade to Fahrenheit conversion: F = 9*C/5 + 32 */ #define B16_9DIV5 (9 * 65536 / 5) #define B16_32 (32 * 65536) /**************************************************************************** * Private ****************************************************************************/ struct lm75_dev_s { FAR struct i2c_master_s *i2c; /* I2C interface */ uint8_t addr; /* I2C address */ bool fahrenheit; /* true: temperature will be reported in fahrenheit */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* I2C Helpers */ static int lm75_i2c_write(FAR struct lm75_dev_s *priv, FAR const uint8_t *buffer, int buflen); static int lm75_i2c_read(FAR struct lm75_dev_s *priv, FAR uint8_t *buffer, int buflen); static int lm75_readb16(FAR struct lm75_dev_s *priv, uint8_t regaddr, FAR b16_t *regvalue); static int lm75_writeb16(FAR struct lm75_dev_s *priv, uint8_t regaddr, b16_t regval); static int lm75_readtemp(FAR struct lm75_dev_s *priv, FAR b16_t *temp); static int lm75_readconf(FAR struct lm75_dev_s *priv, FAR uint8_t *conf); static int lm75_writeconf(FAR struct lm75_dev_s *priv, uint8_t conf); /* Character driver methods */ static int lm75_open(FAR struct file *filep); static int lm75_close(FAR struct file *filep); static ssize_t lm75_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t lm75_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); static int lm75_ioctl(FAR struct file *filep,int cmd,unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_lm75fops = { lm75_open, lm75_close, lm75_read, lm75_write, NULL, lm75_ioctl #ifndef CONFIG_DISABLE_POLL , NULL #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: lm75_i2c_write * * Description: * Write to the I2C device. * ****************************************************************************/ static int lm75_i2c_write(FAR struct lm75_dev_s *priv, FAR const uint8_t *buffer, int buflen) { struct i2c_msg_s msg; /* Setup for the transfer */ msg.frequency = CONFIG_LM75_I2C_FREQUENCY, msg.addr = priv->addr; msg.flags = 0; msg.buffer = (FAR uint8_t *)buffer; /* Override const */ msg.length = buflen; /* Then perform the transfer. */ return I2C_TRANSFER(priv->i2c, &msg, 1); } /**************************************************************************** * Name: lm75_i2c_read * * Description: * Read from the I2C device. * ****************************************************************************/ static int lm75_i2c_read(FAR struct lm75_dev_s *priv, FAR uint8_t *buffer, int buflen) { struct i2c_msg_s msg; /* Setup for the transfer */ msg.frequency = CONFIG_LM75_I2C_FREQUENCY, msg.addr = priv->addr, msg.flags = I2C_M_READ; msg.buffer = buffer; msg.length = buflen; /* Then perform the transfer. */ return I2C_TRANSFER(priv->i2c, &msg, 1); } /**************************************************************************** * Name: lm75_readb16 * * Description: * Read a 16-bit register (LM75_TEMP_REG, LM75_THYS_REG, or LM75_TOS_REG) * ****************************************************************************/ static int lm75_readb16(FAR struct lm75_dev_s *priv, uint8_t regaddr, FAR b16_t *regvalue) { uint8_t buffer[2]; int ret; /* Write the register address */ ret = lm75_i2c_write(priv, ®addr, 1); if (ret < 0) { sndbg("i2c_write failed: %d\n", ret); return ret; } /* Restart and read 16-bits from the register (discarding 7) */ ret = lm75_i2c_read(priv, buffer, 2); if (ret < 0) { sndbg("i2c_read failed: %d\n", ret); return ret; } /* Data format is: TTTTTTTT Txxxxxxx where TTTTTTTTT is a nine-bit, * signed temperature value with LSB = 0.5 degrees centigrade. So the * raw data is b8_t */ *regvalue = b8tob16((b8_t)buffer[0] << 8 | (b8_t)buffer[1]); sndbg("addr: %02x value: %08x ret: %d\n", regaddr, *regvalue, ret); return OK; } /**************************************************************************** * Name: lm75_writeb16 * * Description: * Write to a 16-bit register (LM75_TEMP_REG, LM75_THYS_REG, or LM75_TOS_REG) * ****************************************************************************/ static int lm75_writeb16(FAR struct lm75_dev_s *priv, uint8_t regaddr, b16_t regval) { uint8_t buffer[3]; b8_t regb8; sndbg("addr: %02x value: %08x\n", regaddr, regval); /* Set up a 3 byte message to send */ buffer[0] = regaddr; regb8 = b16tob8(regval); buffer[1] = (uint8_t)(regb8 >> 8); buffer[2] = (uint8_t)regb8; /* Write the register address followed by the data (no RESTART) */ return lm75_i2c_write(priv, buffer, 3); } /**************************************************************************** * Name: lm75_readtemp * * Description: * Read the temperature register with special scaling (LM75_TEMP_REG) * ****************************************************************************/ static int lm75_readtemp(FAR struct lm75_dev_s *priv, FAR b16_t *temp) { b16_t temp16; int ret; /* Read the raw temperature data (b16_t) */ ret = lm75_readb16(priv, LM75_TEMP_REG, &temp16); if (ret < 0) { sndbg("lm75_readb16 failed: %d\n", ret); return ret; } sndbg("Centigrade: %08x\n", temp16); /* Was fahrenheit requested? */ if (priv->fahrenheit) { /* Centigrade to Fahrenheit conversion: F = 9*C/5 + 32 */ temp16 = b16mulb16(temp16, B16_9DIV5) + B16_32; sndbg("Fahrenheit: %08x\n", temp16); } *temp = temp16; return OK; } /**************************************************************************** * Name: lm75_readconf * * Description: * Read the 8-bit LM75 configuration register * ****************************************************************************/ static int lm75_readconf(FAR struct lm75_dev_s *priv, FAR uint8_t *conf) { uint8_t buffer; int ret; /* Write the configuration register address */ buffer = LM75_CONF_REG; ret = lm75_i2c_write(priv, &buffer, 1); if (ret < 0) { sndbg("i2c_write failed: %d\n", ret); return ret; } /* Restart and read 8-bits from the register */ ret = lm75_i2c_read(priv, conf, 1); sndbg("conf: %02x ret: %d\n", *conf, ret); return ret; } /**************************************************************************** * Name: lm75_writeconf * * Description: * Write to a 8-bit LM75 configuration register. * ****************************************************************************/ static int lm75_writeconf(FAR struct lm75_dev_s *priv, uint8_t conf) { uint8_t buffer[2]; sndbg("conf: %02x\n", conf); /* Set up a 2 byte message to send */ buffer[0] = LM75_CONF_REG; buffer[1] = conf; /* Write the register address followed by the data (no RESTART) */ return lm75_i2c_write(priv, buffer, 2); } /**************************************************************************** * Name: lm75_open * * Description: * This function is called whenever the LM-75 device is opened. * ****************************************************************************/ static int lm75_open(FAR struct file *filep) { return OK; } /**************************************************************************** * Name: lm75_close * * Description: * This routine is called when the LM-75 device is closed. * ****************************************************************************/ static int lm75_close(FAR struct file *filep) { return OK; } /**************************************************************************** * Name: lm75_read ****************************************************************************/ static ssize_t lm75_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct inode *inode = filep->f_inode; FAR struct lm75_dev_s *priv = inode->i_private; FAR b16_t *ptr; ssize_t nsamples; int i; int ret; /* How many samples were requested to get? */ nsamples = buflen / sizeof(b16_t); ptr = (FAR b16_t *)buffer; sndbg("buflen: %d nsamples: %d\n", buflen, nsamples); /* Get the requested number of samples */ for (i = 0; i < nsamples; i++) { b16_t temp = 0; /* Read the next b16_t temperature value */ ret = lm75_readtemp(priv, &temp); if (ret < 0) { sndbg("lm75_readtemp failed: %d\n", ret); return (ssize_t)ret; } /* Save the temperature value in the user buffer */ *ptr++ = temp; } return nsamples * sizeof(b16_t); } /**************************************************************************** * Name: lm75_write ****************************************************************************/ static ssize_t lm75_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { return -ENOSYS; } /**************************************************************************** * Name: lm75_ioctl ****************************************************************************/ static int lm75_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode = filep->f_inode; FAR struct lm75_dev_s *priv = inode->i_private; int ret = OK; switch (cmd) { /* Read from the configuration register. Arg: uint8_t* pointer */ case SNIOC_READCONF: { FAR uint8_t *ptr = (FAR uint8_t *)((uintptr_t)arg); DEBUGASSERT(ptr != NULL); ret = lm75_readconf(priv, ptr); sndbg("conf: %02x ret: %d\n", *ptr, ret); } break; /* Wrtie to the configuration register. Arg: uint8_t value */ case SNIOC_WRITECONF: ret = lm75_writeconf(priv, (uint8_t)arg); sndbg("conf: %02x ret: %d\n", *(FAR uint8_t *)arg, ret); break; /* Shutdown the LM75, Arg: None */ case SNIOC_SHUTDOWN: { uint8_t conf; ret = lm75_readconf(priv, &conf); if (ret == OK) { ret = lm75_writeconf(priv, conf | LM75_CONF_SHUTDOWN); } sndbg("conf: %02x ret: %d\n", conf | LM75_CONF_SHUTDOWN, ret); } break; /* Powerup the LM75, Arg: None */ case SNIOC_POWERUP: { uint8_t conf; ret = lm75_readconf(priv, &conf); if (ret == OK) { ret = lm75_writeconf(priv, conf & ~LM75_CONF_SHUTDOWN); } sndbg("conf: %02x ret: %d\n", conf & ~LM75_CONF_SHUTDOWN, ret); } break; /* Report samples in Fahrenheit */ case SNIOC_FAHRENHEIT: priv->fahrenheit = true; sndbg("Fahrenheit\n"); break; /* Report Samples in Centigrade */ case SNIOC_CENTIGRADE: priv->fahrenheit = false; sndbg("Centigrade\n"); break; /* Read THYS temperature register. Arg: b16_t* pointer */ case SNIOC_READTHYS: { FAR b16_t *ptr = (FAR b16_t *)((uintptr_t)arg); DEBUGASSERT(ptr != NULL); ret = lm75_readb16(priv, LM75_THYS_REG, ptr); sndbg("THYS: %08x ret: %d\n", *ptr, ret); } break; /* Write THYS temperature register. Arg: b16_t value */ case SNIOC_WRITETHYS: ret = lm75_writeb16(priv, LM75_THYS_REG, (b16_t)arg); sndbg("THYS: %08x ret: %d\n", (b16_t)arg, ret); break; /* Read TOS (Over-temp Shutdown Threshold) Register. Arg: b16_t* pointer */ case SNIOC_READTOS: { FAR b16_t *ptr = (FAR b16_t *)((uintptr_t)arg); DEBUGASSERT(ptr != NULL); ret = lm75_readb16(priv, LM75_TOS_REG, ptr); sndbg("TOS: %08x ret: %d\n", *ptr, ret); } break; /* Write TOS (Over-temp Shutdown Threshold) Register. Arg: b16_t value */ case SNIOC_WRITETOS: ret = lm75_writeb16(priv, LM75_TOS_REG, (b16_t)arg); sndbg("TOS: %08x ret: %d\n", (b16_t)arg, ret); break; default: sndbg("Unrecognized cmd: %d\n", cmd); ret = -ENOTTY; break; } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: lm75_register * * Description: * Register the LM-75 character device as 'devpath' * * Input Parameters: * devpath - The full path to the driver to register. E.g., "/dev/temp0" * i2c - An instance of the I2C interface to use to communicate with LM75 * addr - The I2C address of the LM-75. The base I2C address of the LM75 * is 0x48. Bits 0-3 can be controlled to get 8 unique addresses from 0x48 * through 0x4f. * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ int lm75_register(FAR const char *devpath, FAR struct i2c_master_s *i2c, uint8_t addr) { FAR struct lm75_dev_s *priv; int ret; /* Sanity check */ DEBUGASSERT(i2c != NULL); DEBUGASSERT(addr == CONFIG_LM75_ADDR0 || addr == CONFIG_LM75_ADDR1 || addr == CONFIG_LM75_ADDR2 || addr == CONFIG_LM75_ADDR3 || addr == CONFIG_LM75_ADDR4 || addr == CONFIG_LM75_ADDR5 || addr == CONFIG_LM75_ADDR6 || addr == CONFIG_LM75_ADDR7); /* Initialize the LM-75 device structure */ priv = (FAR struct lm75_dev_s *)kmm_malloc(sizeof(struct lm75_dev_s)); if (priv == NULL) { sndbg("Failed to allocate instance\n"); return -ENOMEM; } priv->i2c = i2c; priv->addr = addr; priv->fahrenheit = false; /* Register the character driver */ ret = register_driver(devpath, &g_lm75fops, 0666, priv); if (ret < 0) { sndbg("Failed to register driver: %d\n", ret); kmm_free(priv); } return ret; } #endif /* CONFIG_I2C && CONFIG_I2C_LM75 */