| /**************************************************************************** |
| * drivers/1wire/1wire.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * 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 <nuttx/nuttx.h> |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| #include <string.h> |
| |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/1wire/1wire_master.h> |
| #include <nuttx/1wire/1wire.h> |
| |
| #include "1wire_internal.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define MATCH_FAMILY(rom, family) ((family) == 0 || ((rom) & 0xff) == (family)) |
| |
| #ifndef CONFIG_ENDIAN_BIG |
| # define onewire_leuint64(x) (x) |
| # define onewire_leuint32(x) (x) |
| #endif |
| |
| #define NO_HOLDER (INVALID_PROCESS_ID) |
| |
| /**************************************************************************** |
| * 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 |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: onewire_leuint64 |
| * |
| * Description: |
| * Get a 64-bit value stored in little endian order for a big-endian |
| * machine. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_ENDIAN_BIG |
| static inline uint64_t onewire_leuint64(uint64_t x) |
| { |
| return (((x & 0xff00000000000000ull) >> 56) | |
| ((x & 0x00ff000000000000ull) >> 40) | |
| ((x & 0x0000ff0000000000ull) >> 24) | |
| ((x & 0x000000ff00000000ull) >> 8) | |
| ((x & 0x00000000ff000000ull) << 8) | |
| ((x & 0x0000000000ff0000ull) << 24) | |
| ((x & 0x000000000000ff00ull) << 40) | |
| ((x & 0x00000000000000ffull) << 56)); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: onewire_leuint32 |
| * |
| * Description: |
| * Get a 32-bit value stored in little endian order for a big-endian |
| * machine. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_ENDIAN_BIG |
| static inline uint32_t onewire_leuint32(uint32_t x) |
| { |
| return (((x & 0xff000000) >> 24) | |
| ((x & 0x00ff0000) >> 8) | |
| ((x & 0x0000ff00) << 8) | |
| ((x & 0x000000ff) << 24)); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: onewire_addslave_on_search |
| * |
| * Description: |
| * A default callback used to save scanned romcodes into |
| * the onewire_master_s struct. Currently used in GETFAMILYROMS. |
| * |
| ****************************************************************************/ |
| |
| static void onewire_addslave_on_search(int family, uint64_t romcode, |
| FAR void *arg) |
| { |
| DEBUGASSERT(arg != NULL); |
| FAR struct onewire_master_s *master = (FAR struct onewire_master_s *)arg; |
| DEBUGASSERT(master->available_roms != NULL); |
| |
| if (master->nslaves < master->maxslaves) |
| { |
| master->available_roms[master->nslaves] = romcode; |
| master->nslaves += 1; |
| } |
| } |
| |
| /**************************************************************************** |
| * 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; |
| master = container_of(cb, struct onewire_master_s, pm_cb); |
| |
| /* 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 (nxrmutex_is_locked(&master->devlock)) |
| { |
| /* Exclusive lock is held, do not allow entry to deeper PM |
| * states. |
| */ |
| |
| return -EBUSY; |
| } |
| |
| break; |
| |
| default: |
| |
| /* Should not get here */ |
| |
| break; |
| } |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: onewire_reset_resume |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| int onewire_reset_resume(FAR struct onewire_master_s *master) |
| { |
| int ret; |
| uint8_t buf[] = |
| { |
| ONEWIRE_CMD_RESUME |
| }; |
| |
| ret = ONEWIRE_RESET(master->dev); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| return ONEWIRE_WRITE(master->dev, buf, 1); |
| } |
| |
| /**************************************************************************** |
| * Name: onewire_reset_select |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| int onewire_reset_select(FAR struct onewire_master_s *master, |
| uint64_t romcode) |
| { |
| uint64_t tmp; |
| uint8_t skip_rom[1] = |
| { |
| ONEWIRE_CMD_SKIP_ROM |
| }; |
| |
| uint8_t match_rom[9] = |
| { |
| ONEWIRE_CMD_MATCH_ROM, 0 |
| }; |
| |
| int ret; |
| |
| ret = ONEWIRE_RESET(master->dev); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Issue skip-ROM if single slave, match-ROM otherwise. */ |
| |
| if (master->nslaves == 1 || master->maxslaves == 1) |
| { |
| ret = ONEWIRE_WRITE(master->dev, skip_rom, sizeof(skip_rom)); |
| } |
| else |
| { |
| tmp = onewire_leuint64(romcode); |
| memcpy(&match_rom[1], &tmp, 8); |
| ret = ONEWIRE_WRITE(master->dev, match_rom, sizeof(match_rom)); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: onewire_readrom |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| int onewire_readrom(FAR struct onewire_master_s *master, FAR uint64_t *rom) |
| { |
| uint8_t txbuf[] = |
| { |
| ONEWIRE_CMD_READ_ROM |
| }; |
| |
| uint8_t rxbuf[8] = |
| { |
| 0 |
| }; |
| |
| uint64_t tmp = -1; |
| int ret; |
| |
| DEBUGASSERT(master != NULL && rom != NULL); |
| |
| /* Read ROM-code of single connected slave and check its checksum. */ |
| |
| ret = ONEWIRE_RESET(master->dev); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| ret = ONEWIRE_WRITE(master->dev, txbuf, sizeof(txbuf)); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| ret = ONEWIRE_READ(master->dev, rxbuf, sizeof(rxbuf)); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| #ifdef CONFIG_DEBUG_I2C_INFO |
| lib_dumpbuffer("onewire_readrom: rxbuf", rxbuf, sizeof(rxbuf)); |
| #endif |
| |
| tmp = onewire_leuint64(*(uint64_t *)rxbuf); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!onewire_valid_rom(tmp)) |
| { |
| i2cerr("ERROR: crc8 does not match!\n"); |
| ret = -EIO; |
| } |
| #endif |
| |
| *rom = tmp; |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: onewire_triplet |
| * |
| * Description: |
| * Used by 1-wire search algorithm. Reads two bits and writes |
| * one based on comparison of read bits. |
| * |
| * Input Parameters: |
| * search_bit - Bit to write if both id_bit and cmp_id_bit match |
| * |
| * Output Parameters: |
| * taken_bit - Bit indicating the direction where the search is |
| * progressing. |
| * |
| * Return Value: |
| * Number of valid bits or negative on error. |
| * |
| ****************************************************************************/ |
| |
| int onewire_triplet(FAR struct onewire_master_s *master, |
| uint8_t search_bit, |
| FAR uint8_t *taken_bit) |
| { |
| int ret; |
| int nvalid; |
| uint8_t id_bit; |
| uint8_t cmp_id_bit; |
| |
| ret = ONEWIRE_READBIT(master->dev, &id_bit); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| ret = ONEWIRE_READBIT(master->dev, &cmp_id_bit); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| if (id_bit == 1 && cmp_id_bit == 1) |
| { |
| /* No devices on bus */ |
| |
| return 0; |
| } |
| else if (id_bit != cmp_id_bit) |
| { |
| /* Only one bit is valid, search to that direction. */ |
| |
| nvalid = 1; |
| *taken_bit = id_bit; |
| } |
| else |
| { |
| /* Two bits are valid, search to pre-determined direction. */ |
| |
| nvalid = 2; |
| *taken_bit = search_bit; |
| } |
| |
| ret = ONEWIRE_WRITEBIT(master->dev, taken_bit); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| return nvalid; |
| } |
| |
| /**************************************************************************** |
| * Name: onewire_search |
| * |
| * Description: |
| * Search all devices from a 1-wire network. This is the 1-wire search |
| * algorithm from Maxim Application Note 187. |
| * |
| * Input Parameters: |
| * master - Pointer to the allocated 1-wire interface |
| * family - Limit search to devices of matching family |
| * alarmonly - Limit search to devices on alarm state |
| * cb_search - Callback to call on each device found |
| * arg - Argument passed to cb_search |
| * |
| * Return Value: |
| * Number of slaves present and matching family. |
| * |
| ****************************************************************************/ |
| |
| int onewire_search(FAR struct onewire_master_s *master, |
| int family, |
| bool alarmonly, |
| CODE void (*cb_search)(int family, |
| uint64_t romcode, |
| FAR void *arg), |
| FAR void *arg) |
| { |
| FAR struct onewire_dev_s *dev = master->dev; |
| uint8_t cmd[1]; |
| uint64_t rom = 0; |
| uint64_t last_rom; |
| int last_zero = -1; |
| int last_bit = 64; /* bit of last discrepancy */ |
| int nslaves = 0; |
| int nslaves_match = 0; |
| int ret; |
| |
| /* Disallow nested search. */ |
| |
| DEBUGASSERT(master->insearch == false); |
| |
| /* Make complete search on the bus mutal exclusive */ |
| |
| ret = nxrmutex_lock(&master->devlock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Skip costly search if bus supports only a single slave. */ |
| |
| if (master->maxslaves == 1) |
| { |
| ret = onewire_readrom(master, &rom); |
| if (ret >= 0 && MATCH_FAMILY(rom, family)) |
| { |
| master->insearch = true; |
| cb_search(rom & 0xff, rom, arg); |
| master->insearch = false; |
| nslaves_match++; |
| } |
| |
| goto unlock; |
| } |
| |
| /* Select search type. */ |
| |
| cmd[0] = alarmonly ? ONEWIRE_CMD_ALARM_SEARCH : ONEWIRE_CMD_SEARCH; |
| |
| while (nslaves++ < master->maxslaves) |
| { |
| int i; |
| |
| /* Reset bus so slaves are ready to answer our probing. */ |
| |
| ret = ONEWIRE_RESET(dev); |
| if (ret < 0) |
| { |
| goto unlock; |
| } |
| |
| /* Send the Search-ROM command. */ |
| |
| ret = ONEWIRE_WRITE(dev, cmd, sizeof(cmd)); |
| if (ret < 0) |
| { |
| goto unlock; |
| } |
| |
| last_rom = rom; |
| rom = 0; |
| |
| /* TODO: setup initial rom from family to reduce search space. */ |
| |
| for (i = 0; i < 64; i++) |
| { |
| uint8_t search_bit; |
| uint8_t taken_bit; |
| |
| /* Setup search direction. */ |
| |
| if (i == last_bit) |
| { |
| search_bit = 1; |
| } |
| else if (i > last_bit) |
| { |
| search_bit = 0; |
| } |
| else |
| { |
| search_bit = !!(last_rom & (1 << i)); |
| } |
| |
| ret = onewire_triplet(master, search_bit, &taken_bit); |
| if (ret <= 0) |
| { |
| /* Error or zero valid directions. */ |
| |
| goto unlock; |
| } |
| |
| if (ret == 2 && taken_bit == 0) |
| { |
| /* If both directions were valid, and we took the 0 path, |
| * remember this. |
| */ |
| |
| last_zero = i; |
| } |
| |
| /* Update rom code from taken bit. */ |
| |
| rom |= (uint64_t)taken_bit << i; |
| } |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!onewire_valid_rom(rom)) |
| { |
| i2cerr("ERROR: crc8 does not match!\n"); |
| } |
| #endif |
| |
| if (last_zero == last_bit || last_zero == -1) |
| { |
| i2cinfo("search complete, rom=0x%" PRIx64 "\n", rom); |
| |
| /* Found last device, quit searching. */ |
| |
| if (MATCH_FAMILY(rom, family)) |
| { |
| master->insearch = true; |
| cb_search(rom & 0xff, rom, arg); |
| master->insearch = false; |
| nslaves_match++; |
| } |
| break; |
| } |
| |
| last_bit = last_zero; |
| |
| /* Found device, will keep looking for more. */ |
| |
| if (MATCH_FAMILY(rom, family)) |
| { |
| master->insearch = true; |
| cb_search(rom & 0xff, rom, arg); |
| master->insearch = false; |
| nslaves_match++; |
| } |
| |
| /* TODO: does not handle case when there are more than maxslaves |
| * slaves present in the bus. |
| */ |
| } |
| |
| unlock: |
| nxrmutex_unlock(&master->devlock); |
| return (ret < 0) ? ret : nslaves_match; |
| } |
| |
| /**************************************************************************** |
| * Name: onewire_addslave |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| int onewire_addslave(FAR struct onewire_master_s *master, |
| FAR struct onewire_slave_s *slave) |
| { |
| DEBUGASSERT(master != NULL && slave != NULL); |
| DEBUGASSERT(slave->master == NULL); |
| |
| if (master->nslaves >= master->maxslaves) |
| { |
| return -EAGAIN; |
| } |
| |
| /* TODO: linked list of slaves? */ |
| |
| master->nslaves++; |
| slave->master = master; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: onewire_removeslave |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| int onewire_removeslave(FAR struct onewire_master_s *master, |
| FAR struct onewire_slave_s *slave) |
| { |
| DEBUGASSERT(master != NULL && slave != NULL); |
| DEBUGASSERT(master->nslaves > 0 && slave->master == master); |
| |
| /* TODO: linked list of slaves? */ |
| |
| master->nslaves--; |
| slave->master = NULL; |
| return OK; |
| } |
| |
| int onewire_ioctl_setrom(FAR struct onewire_master_s *master, uint64_t rom) |
| { |
| DEBUGASSERT(master != NULL); |
| master->selected_rom = rom; |
| return OK; |
| } |
| |
| int onewire_ioctl_getfamilyroms(FAR struct onewire_master_s *master, |
| FAR struct onewire_availroms_s *avail, |
| int family) |
| { |
| DEBUGASSERT(master != NULL && avail != NULL); |
| |
| /* Perform scan of the bus, but first restore the count */ |
| |
| master->nslaves = 0; |
| onewire_search(master, family, false, onewire_addslave_on_search, master); |
| |
| /* Very secure for loop! */ |
| |
| avail->actual = 0; |
| for (int i = 0; (i < master->nslaves) && (i < avail->maxroms) && |
| (i < master->maxslaves); ++i) |
| { |
| avail->roms[i] = master->available_roms[i]; |
| avail->actual += 1; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: onewire_initialize |
| * |
| * Description: |
| * Return 1-wire bus master from 1-wire lower half device. |
| * |
| * Input Parameters: |
| * dev - Pointer to the allocated 1-wire lower half |
| * maxslaves - Maximum number of 1-wire slave devices |
| * |
| ****************************************************************************/ |
| |
| FAR struct onewire_master_s * |
| onewire_initialize(FAR struct onewire_dev_s *dev, int maxslaves) |
| { |
| FAR struct onewire_master_s *master; |
| FAR uint64_t *roms_array; |
| |
| DEBUGASSERT(dev != NULL); |
| DEBUGASSERT(maxslaves > 0); |
| |
| master = (FAR struct onewire_master_s *) |
| kmm_malloc(sizeof(struct onewire_master_s)); |
| if (master == NULL) |
| { |
| i2cerr("ERROR: Failed to allocate\n"); |
| return NULL; |
| } |
| |
| roms_array = (FAR uint64_t *)kmm_malloc(sizeof(uint64_t) * maxslaves); |
| if (roms_array == NULL) |
| { |
| i2cerr("ERROR: Failed to allocate\n"); |
| kmm_free(master); |
| return NULL; |
| } |
| |
| /* Initialize the device structure */ |
| |
| master->dev = dev; |
| nxrmutex_init(&master->devlock); |
| master->nslaves = 0; |
| master->maxslaves = maxslaves; |
| master->insearch = false; |
| master->available_roms = roms_array; |
| |
| #ifdef CONFIG_PM |
| master->pm_cb.prepare = onewire_pm_prepare; |
| |
| /* Register to receive power management callbacks */ |
| |
| 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 */ |
| |
| nxrmutex_destroy(&master->devlock); |
| kmm_free(master->available_roms); |
| kmm_free(master); |
| return OK; |
| } |