From 3cf830bd67cae8694eb8c2dda1812f8d07d56cde Mon Sep 17 00:00:00 2001 From: Juha Niskanen Date: Tue, 24 Apr 2018 13:21:02 +0000 Subject: [PATCH] Merged in juniskane/nuttx_stm32l4/1wire_sht21_changes_pr (pull request #631) drivers/1wire: add PM hooks and unlink support to sht21 sensor (allowing hot-swappable I2C bus from 1-wire converter) * drivers/1wire: add PM hook to 1wire bus * drivers/1wire/ds28e17.c: i2c instance must be zeroed * drivers/sht21: add unlink support Unlike most other unlink implementations in NuttX drivers, this one does not allow any I2C operations after unlink, making it possible to deallocate the I2C bus. Approved-by: Gregory Nutt --- drivers/1wire/1wire.c | 126 +++++++++++++++++++++++- drivers/1wire/1wire_internal.h | 17 ++++ drivers/1wire/ds28e17.c | 4 +- drivers/sensors/sht21.c | 169 +++++++++++++++++++++++++++++++-- 4 files changed, 303 insertions(+), 13 deletions(-) diff --git a/drivers/1wire/1wire.c b/drivers/1wire/1wire.c index 34344253a8..61ea0f7885 100644 --- a/drivers/1wire/1wire.c +++ b/drivers/1wire/1wire.c @@ -62,6 +62,15 @@ #define NO_HOLDER ((pid_t)-1) +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +#ifdef CONFIG_PM +static int onewire_pm_prepare(FAR struct pm_callback_s *cb, int domain, + enum pm_state_e pmstate); +#endif + /**************************************************************************** * Private Functions ****************************************************************************/ @@ -122,6 +131,87 @@ static inline void onewire_sem_init(FAR struct onewire_sem_s *sem) nxsem_init(&sem->sem, 0, 1); } +/**************************************************************************** + * Name: onewire_sem_destroy + * + * Description: + * + ****************************************************************************/ + +static inline void onewire_sem_destroy(FAR struct onewire_sem_s *sem) +{ + nxsem_destroy(&sem->sem); +} + +/************************************************************************************ + * Name: onewire_pm_prepare + * + * Description: + * Request the driver to prepare for a new power state. This is a + * warning that the system is about to enter into a new power state. The + * driver should begin whatever operations that may be required to enter + * power state. The driver may abort the state change mode by returning + * a non-zero value from the callback function. + * + * Input Parameters: + * cb - Returned to the driver. The driver version of the callback + * structure may include additional, driver-specific state + * data at the end of the structure. + * domain - Identifies the activity domain of the state change + * pmstate - Identifies the new PM state + * + * Returned Value: + * 0 (OK) means the event was successfully processed and that the driver + * is prepared for the PM state change. Non-zero means that the driver + * is not prepared to perform the tasks needed achieve this power setting + * and will cause the state change to be aborted. NOTE: The prepare + * method will also be recalled when reverting from lower back to higher + * power consumption modes (say because another driver refused a lower + * power state change). Drivers are not permitted to return non-zero + * values when reverting back to higher power consumption modes! + * + ************************************************************************************/ +#ifdef CONFIG_PM +static int onewire_pm_prepare(FAR struct pm_callback_s *cb, int domain, + enum pm_state_e pmstate) +{ + struct onewire_master_s *master = + (struct onewire_master_s *)((char *)cb - + offsetof(struct onewire_master_s, pm_cb)); + int sval; + + /* Logic to prepare for a reduced power state goes here. */ + + switch (pmstate) + { + case PM_NORMAL: + case PM_IDLE: + break; + + case PM_STANDBY: + case PM_SLEEP: + /* Check if exclusive lock for the bus master is held. */ + + if (nxsem_getvalue(&master->devsem.sem, &sval) < 0) + { + DEBUGASSERT(false); + return -EINVAL; + } + + if (sval <= 0) + { + /* Exclusive lock is held, do not allow entry to deeper PM states. */ + + return -EBUSY; + } + + break; + } + + return OK; +} +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -167,7 +257,7 @@ void onewire_sem_wait(FAR struct onewire_master_s *master) } while (ret == -EINTR); - /* No we hold the semaphore */ + /* Now we hold the semaphore */ master->devsem.holder = me; master->devsem.count = 1; @@ -636,5 +726,39 @@ FAR struct onewire_master_s * master->maxslaves = maxslaves; master->insearch = false; +#ifdef CONFIG_PM + master->pm_cb.prepare = onewire_pm_prepare; + + /* Register to receive power management callbacks */ + + (void)pm_register(&master->pm_cb); +#endif + return master; } + +/**************************************************************************** + * Name: onewire_uninitialize + * + * Description: + * Release 1-wire bus master. + * + * Input Parameters: + * master - Pointer to the allocated 1-wire master + * + ****************************************************************************/ + +int onewire_uninitialize(FAR struct onewire_master_s *master) +{ +#ifdef CONFIG_PM + /* Unregister power management callbacks */ + + pm_unregister(&master->pm_cb); +#endif + + /* Release resources. This does not touch the underlying onewire_dev_s */ + + onewire_sem_destroy(&master->devsem); + kmm_free(master); + return OK; +} diff --git a/drivers/1wire/1wire_internal.h b/drivers/1wire/1wire_internal.h index 402abd92eb..c981d93f55 100644 --- a/drivers/1wire/1wire_internal.h +++ b/drivers/1wire/1wire_internal.h @@ -44,6 +44,7 @@ #include #include +#include /**************************************************************************** * Public Types @@ -65,6 +66,9 @@ struct onewire_master_s int nslaves; /* Number of 1-wire slaves */ int maxslaves; /* Maximum number of 1-wire slaves */ bool insearch; /* If we are in middle of 1-wire search */ +#ifdef CONFIG_PM + struct pm_callback_s pm_cb; /* PM callbacks */ +#endif }; struct onewire_slave_s @@ -177,4 +181,17 @@ int onewire_search(FAR struct onewire_master_s *master, FAR struct onewire_master_s * onewire_initialize(FAR struct onewire_dev_s *dev, int maxslaves); +/**************************************************************************** + * Name: onewire_uninitialize + * + * Description: + * Release 1-wire bus master. + * + * Input Parameters: + * master - Pointer to the allocated 1-wire master + * + ****************************************************************************/ + +int onewire_uninitialize(FAR struct onewire_master_s *master); + #endif /* __DRIVERS_1WIRE_1WIRE_INTERNAL_H */ diff --git a/drivers/1wire/ds28e17.c b/drivers/1wire/ds28e17.c index fd64752ccc..794f19a3cf 100644 --- a/drivers/1wire/ds28e17.c +++ b/drivers/1wire/ds28e17.c @@ -765,7 +765,7 @@ FAR struct i2c_master_s * /* Allocate instance */ - inst = kmm_malloc(sizeof(struct ds_i2c_inst_s)); + inst = kmm_zalloc(sizeof(struct ds_i2c_inst_s)); if (inst == NULL) { i2cerr("ERROR: Failed to allocate instance\n"); @@ -778,12 +778,12 @@ FAR struct i2c_master_s * inst->master = master; inst->frequency = 400000; /* power-on frequency */ inst->stretch = DS_DEFAULT_STRETCH; + inst->slave.romcode = romcode; /* We need a recursive lock as this may be called from a search callback. */ onewire_sem_wait(master); - inst->slave.romcode = romcode; if (onewire_addslave(master, &inst->slave) < 0) { kmm_free(inst); diff --git a/drivers/sensors/sht21.c b/drivers/sensors/sht21.c index ccbdb3f025..9190ea13b2 100644 --- a/drivers/sensors/sht21.c +++ b/drivers/sensors/sht21.c @@ -84,9 +84,15 @@ struct sht21_dev_s FAR struct i2c_master_s *i2c; /* I2C interface */ uint8_t addr; /* I2C address */ bool valid; /* If cached readings are valid */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + bool unlinked; /* True, driver has been unlinked */ +#endif struct timespec last_update; /* Last time when sensor was read */ int temperature; /* Cached temperature */ int humidity; /* Cached humidity */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + int16_t crefs; /* Number of open references */ +#endif sem_t devsem; }; @@ -107,14 +113,19 @@ static int sht21_read16(FAR struct sht21_dev_s *priv, uint8_t regaddr, /* Character driver methods */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int sht21_open(FAR struct file *filep); static int sht21_close(FAR struct file *filep); +#endif static ssize_t sht21_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t sht21_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); static int sht21_ioctl(FAR struct file *filep, int cmd, unsigned long arg); +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS +static int sht21_unlink(FAR struct inode *inode); +#endif /**************************************************************************** * Private Data @@ -122,17 +133,22 @@ static int sht21_ioctl(FAR struct file *filep, int cmd, static const struct file_operations g_sht21fops = { - sht21_open, /* open */ - sht21_close, /* close */ - sht21_read, /* read */ - sht21_write, /* write */ - NULL, /* seek */ - sht21_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + sht21_open, /* open */ + sht21_close, /* close */ +#else + NULL, /* open */ + NULL, /* close */ +#endif + sht21_read, /* read */ + sht21_write, /* write */ + NULL, /* seek */ + sht21_ioctl /* ioctl */ #ifndef CONFIG_DISABLE_POLL - , NULL /* poll */ + , NULL /* poll */ #endif #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS - , NULL /* unlink */ + , sht21_unlink /* unlink */ #endif }; @@ -362,10 +378,36 @@ static int sht21_read_values(FAR struct sht21_dev_s *priv, FAR int *temp, * ****************************************************************************/ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int sht21_open(FAR struct file *filep) { + FAR struct inode *inode = filep->f_inode; + FAR struct sht21_dev_s *priv = inode->i_private; + int ret; + + /* Get exclusive access */ + + do + { + ret = nxsem_wait(&priv->devsem); + + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + DEBUGASSERT(ret == OK || ret == -EINTR); + } + while (ret == -EINTR); + + /* Increment the count of open references on the driver */ + + priv->crefs++; + DEBUGASSERT(priv->crefs > 0); + + nxsem_post(&priv->devsem); return OK; } +#endif /**************************************************************************** * Name: sht21_close @@ -375,10 +417,47 @@ static int sht21_open(FAR struct file *filep) * ****************************************************************************/ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int sht21_close(FAR struct file *filep) { + FAR struct inode *inode = filep->f_inode; + FAR struct sht21_dev_s *priv = inode->i_private; + int ret; + + /* Get exclusive access */ + + do + { + ret = nxsem_wait(&priv->devsem); + + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + DEBUGASSERT(ret == OK || ret == -EINTR); + } + while (ret == -EINTR); + + /* Decrement the count of open references on the driver */ + + DEBUGASSERT(priv->crefs > 0); + priv->crefs--; + + /* If the count has decremented to zero and the driver has been unlinked, + * then free memory now. + */ + + if (priv->crefs <= 0 && priv->unlinked) + { + nxsem_destroy(&priv->devsem); + kmm_free(priv); + return OK; + } + + nxsem_post(&priv->devsem); return OK; } +#endif /**************************************************************************** * Name: sht21_read @@ -386,7 +465,7 @@ static int sht21_close(FAR struct file *filep) static ssize_t sht21_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { - FAR struct inode *inode = filep->f_inode; + FAR struct inode *inode = filep->f_inode; FAR struct sht21_dev_s *priv = inode->i_private; ssize_t length = 0; int temp; @@ -407,6 +486,18 @@ static ssize_t sht21_read(FAR struct file *filep, FAR char *buffer, size_t bufle } while (ret == -EINTR); +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + if (priv->unlinked) + { + /* Do not allow operations on unlinked sensors. This allows + * sensor use on hot swappable I2C bus. + */ + + nxsem_post(&priv->devsem); + return -ENODEV; + } +#endif + ret = sht21_read_values(priv, &temp, &rh); if (ret < 0) { @@ -444,7 +535,7 @@ static ssize_t sht21_write(FAR struct file *filep, FAR const char *buffer, static int sht21_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode = filep->f_inode; - FAR struct sht21_dev_s *priv = inode->i_private; + FAR struct sht21_dev_s *priv = inode->i_private; int ret; /* Get exclusive access */ @@ -461,6 +552,18 @@ static int sht21_ioctl(FAR struct file *filep, int cmd, unsigned long arg) } while (ret == -EINTR); +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + if (priv->unlinked) + { + /* Do not allow operations on unlinked sensors. This allows + * sensor use on hot swappable I2C bus. + */ + + nxsem_post(&priv->devsem); + return -ENODEV; + } +#endif + switch (cmd) { /* Soft reset the SHT2x, Arg: None */ @@ -514,6 +617,52 @@ static int sht21_ioctl(FAR struct file *filep, int cmd, unsigned long arg) return ret; } +/**************************************************************************** + * Name: sht21_unlink + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS +static int sht21_unlink(FAR struct inode *inode) +{ + FAR struct sht21_dev_s *priv; + int ret; + + DEBUGASSERT(inode != NULL && inode->i_private != NULL); + priv = (FAR struct sht21_dev_s *)inode->i_private; + + /* Get exclusive access */ + + do + { + ret = nxsem_wait(&priv->devsem); + + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + DEBUGASSERT(ret == OK || ret == -EINTR); + } + while (ret == -EINTR); + + /* Are there open references to the driver data structure? */ + + if (priv->crefs <= 0) + { + nxsem_destroy(&priv->devsem); + kmm_free(priv); + return OK; + } + + /* No... just mark the driver as unlinked and free the resources when + * the last client closes their reference to the driver. + */ + + priv->unlinked = true; + nxsem_post(&priv->devsem); + return ret; +} +#endif + /**************************************************************************** * Public Functions ****************************************************************************/