| // Copyright lowRISC contributors. | 
 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
 | // SPDX-License-Identifier: Apache-2.0 | 
 |  | 
 | #include "sw/device/lib/dif/dif_gpio.h" | 
 |  | 
 | #include "gpio_regs.h"  // Generated. | 
 |  | 
 | static_assert(kDifGpioNumPins <= 32, | 
 |               "This implementation assumes that the number of pins is less " | 
 |               "than or equal to 32"); | 
 |  | 
 | /** | 
 |  * Gives the mask that corresponds to the given bit index. | 
 |  * | 
 |  * @param index Bit index in a 32-bit register. | 
 |  */ | 
 | static uint32_t index_to_mask(uint32_t index) { return 1u << index; } | 
 |  | 
 | /** | 
 |  * Perform a masked write to a GPIO register. | 
 |  * | 
 |  * The GPIO device provides masked bit-level atomic writes to its DIRECT_OUT | 
 |  * and DIRECT_OE registers. This allows software to modify half of the bits | 
 |  * at a time without requiring a read-modify-write. Note that depending on the | 
 |  * value of the `mask`, this function may perform two writes. | 
 |  * | 
 |  * For instance, DIRECT_OUT's lower and upper halves can be modified by | 
 |  * MASKED_OUT_LOWER and MASKED_OUT_UPPER, respectively. Upper half of | 
 |  * MASKED_OUT_LOWER is the mask that determines which bits in the lower half of | 
 |  * DIRECT_OUT will be modified, while lower half of MASKED_OUT_LOWER determines | 
 |  * their values. MASKED_OUT_UPPER behaves in the same way for modifying the | 
 |  * upper half of DIRECT_OUT. | 
 |  * | 
 |  * @param gpio GPIO instance. | 
 |  * @param reg_lower_offset Offset of the masked access register that corresponds | 
 |  * to the lower half of the actual register. | 
 |  * @param reg_upper_offset Offset of the masked access register that corresponds | 
 |  * to the upper half of the actual register. | 
 |  * @param mask Mask that identifies the bits to write to. | 
 |  * @param val Value to write. | 
 |  */ | 
 | OT_WARN_UNUSED_RESULT | 
 | static dif_result_t gpio_masked_write(const dif_gpio_t *gpio, | 
 |                                       ptrdiff_t reg_lower_offset, | 
 |                                       ptrdiff_t reg_upper_offset, uint32_t mask, | 
 |                                       uint32_t val) { | 
 |   if (gpio == NULL) { | 
 |     return kDifBadArg; | 
 |   } | 
 |  | 
 |   const uint32_t mask_lower_half = mask & 0x0000FFFFu; | 
 |   const uint32_t mask_upper_half = mask & 0xFFFF0000u; | 
 |   if (mask_lower_half != 0) { | 
 |     mmio_region_write32(gpio->base_addr, reg_lower_offset, | 
 |                         (mask_lower_half << 16) | (val & 0x0000FFFFu)); | 
 |   } | 
 |   if (mask_upper_half != 0) { | 
 |     mmio_region_write32(gpio->base_addr, reg_upper_offset, | 
 |                         mask_upper_half | ((val & 0xFFFF0000u) >> 16)); | 
 |   } | 
 |  | 
 |   return kDifOk; | 
 | } | 
 |  | 
 | /** | 
 |  * Perform a masked write to a single bit of a GPIO register. | 
 |  * | 
 |  * The GPIO device provides masked bit-level atomic writes to its DIRECT_OUT | 
 |  * and DIRECT_OE registers. This allows software to modify half of the bits | 
 |  * at a time without requiring a read-modify-write. This function is guaranteed | 
 |  * to perform only one write since it never needs to access both halves of a | 
 |  * register. | 
 |  * | 
 |  * See also `gpio_masked_write()`. | 
 |  * | 
 |  * @param gpio GPIO instance. | 
 |  * @param reg_lower_offset Offset of the masked access register that corresponds | 
 |  * to the lower half of the actual register. | 
 |  * @param reg_upper_offset Offset of the masked access register that corresponds | 
 |  * to the upper half of the actual register. | 
 |  * @param index Zero-based index of the bit to write to. | 
 |  * @param val Value to write. | 
 |  */ | 
 | OT_WARN_UNUSED_RESULT | 
 | static dif_result_t gpio_masked_bit_write(const dif_gpio_t *gpio, | 
 |                                           ptrdiff_t reg_lower_offset, | 
 |                                           ptrdiff_t reg_upper_offset, | 
 |                                           uint32_t index, bool val) { | 
 |   if (gpio == NULL || index >= kDifGpioNumPins) { | 
 |     return kDifBadArg; | 
 |   } | 
 |  | 
 |   // Write to reg_lower_offset if the bit is in the lower half, write to | 
 |   // reg_upper_offset otherwise. | 
 |   const ptrdiff_t offset = (index < 16) ? reg_lower_offset : reg_upper_offset; | 
 |   // Since masked access writes to half of a register, index mod 16 gives the | 
 |   // index of the bit in the half-word mask. | 
 |   const uint32_t mask = index_to_mask(index % 16); | 
 |   mmio_region_write32(gpio->base_addr, offset, | 
 |                       (mask << 16) | (val ? mask : 0u)); | 
 |  | 
 |   return kDifOk; | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_reset(const dif_gpio_t *gpio) { | 
 |   if (gpio == NULL) { | 
 |     return kDifBadArg; | 
 |   } | 
 |  | 
 |   // We don't need to write to `GPIO_MASKED_OE_*` and `GPIO_MASKED_OUT_*` | 
 |   // since we directly reset `GPIO_DIRECT_OE` and `GPIO_DIRECT_OUT` below. | 
 |   mmio_region_write32(gpio->base_addr, GPIO_INTR_ENABLE_REG_OFFSET, 0); | 
 |   mmio_region_write32(gpio->base_addr, GPIO_DIRECT_OE_REG_OFFSET, 0); | 
 |   mmio_region_write32(gpio->base_addr, GPIO_DIRECT_OUT_REG_OFFSET, 0); | 
 |   mmio_region_write32(gpio->base_addr, GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, 0); | 
 |   mmio_region_write32(gpio->base_addr, GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET, 0); | 
 |   mmio_region_write32(gpio->base_addr, GPIO_INTR_CTRL_EN_LVLHIGH_REG_OFFSET, 0); | 
 |   mmio_region_write32(gpio->base_addr, GPIO_INTR_CTRL_EN_LVLLOW_REG_OFFSET, 0); | 
 |   mmio_region_write32(gpio->base_addr, GPIO_CTRL_EN_INPUT_FILTER_REG_OFFSET, 0); | 
 |   // Also clear all pending interrupts | 
 |   mmio_region_write32(gpio->base_addr, GPIO_INTR_STATE_REG_OFFSET, 0xFFFFFFFFu); | 
 |  | 
 |   return kDifOk; | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_irq_set_trigger(const dif_gpio_t *gpio, | 
 |                                       dif_gpio_mask_t mask, | 
 |                                       dif_gpio_irq_trigger_t trigger) { | 
 |   if (gpio == NULL) { | 
 |     return kDifBadArg; | 
 |   } | 
 |  | 
 |   // Disable all interrupt triggers for the given mask. | 
 |   mmio_region_nonatomic_clear_mask32( | 
 |       gpio->base_addr, GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, mask, 0); | 
 |   mmio_region_nonatomic_clear_mask32( | 
 |       gpio->base_addr, GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET, mask, 0); | 
 |   mmio_region_nonatomic_clear_mask32( | 
 |       gpio->base_addr, GPIO_INTR_CTRL_EN_LVLHIGH_REG_OFFSET, mask, 0); | 
 |   mmio_region_nonatomic_clear_mask32( | 
 |       gpio->base_addr, GPIO_INTR_CTRL_EN_LVLLOW_REG_OFFSET, mask, 0); | 
 |  | 
 |   switch (trigger) { | 
 |     case kDifGpioIrqTriggerEdgeRising: | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, mask, 0); | 
 |       break; | 
 |     case kDifGpioIrqTriggerEdgeFalling: | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET, mask, 0); | 
 |       break; | 
 |     case kDifGpioIrqTriggerLevelLow: | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_LVLLOW_REG_OFFSET, mask, 0); | 
 |       break; | 
 |     case kDifGpioIrqTriggerLevelHigh: | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_LVLHIGH_REG_OFFSET, mask, 0); | 
 |       break; | 
 |     case kDifGpioIrqTriggerEdgeRisingFalling: | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, mask, 0); | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET, mask, 0); | 
 |       break; | 
 |     case kDifGpioIrqTriggerEdgeRisingLevelLow: | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, mask, 0); | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_LVLLOW_REG_OFFSET, mask, 0); | 
 |       break; | 
 |     case kDifGpioIrqTriggerEdgeFallingLevelHigh: | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET, mask, 0); | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_INTR_CTRL_EN_LVLHIGH_REG_OFFSET, mask, 0); | 
 |       break; | 
 |     default: | 
 |       return kDifError; | 
 |   } | 
 |  | 
 |   return kDifOk; | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_read_all(const dif_gpio_t *gpio, | 
 |                                dif_gpio_state_t *state) { | 
 |   if (gpio == NULL || state == NULL) { | 
 |     return kDifBadArg; | 
 |   } | 
 |  | 
 |   *state = mmio_region_read32(gpio->base_addr, GPIO_DATA_IN_REG_OFFSET); | 
 |  | 
 |   return kDifOk; | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_read(const dif_gpio_t *gpio, dif_gpio_pin_t pin, | 
 |                            bool *state) { | 
 |   if (gpio == NULL || state == NULL || pin >= kDifGpioNumPins) { | 
 |     return kDifBadArg; | 
 |   } | 
 |  | 
 |   *state = mmio_region_get_bit32(gpio->base_addr, GPIO_DATA_IN_REG_OFFSET, pin); | 
 |  | 
 |   return kDifOk; | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_write_all(const dif_gpio_t *gpio, | 
 |                                 dif_gpio_state_t state) { | 
 |   if (gpio == NULL) { | 
 |     return kDifBadArg; | 
 |   } | 
 |  | 
 |   mmio_region_write32(gpio->base_addr, GPIO_DIRECT_OUT_REG_OFFSET, state); | 
 |  | 
 |   return kDifOk; | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_write(const dif_gpio_t *gpio, dif_gpio_pin_t pin, | 
 |                             bool state) { | 
 |   return gpio_masked_bit_write(gpio, GPIO_MASKED_OUT_LOWER_REG_OFFSET, | 
 |                                GPIO_MASKED_OUT_UPPER_REG_OFFSET, pin, state); | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_write_masked(const dif_gpio_t *gpio, dif_gpio_mask_t mask, | 
 |                                    dif_gpio_state_t state) { | 
 |   return gpio_masked_write(gpio, GPIO_MASKED_OUT_LOWER_REG_OFFSET, | 
 |                            GPIO_MASKED_OUT_UPPER_REG_OFFSET, mask, state); | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_output_set_enabled_all(const dif_gpio_t *gpio, | 
 |                                              dif_gpio_state_t state) { | 
 |   if (gpio == NULL) { | 
 |     return kDifBadArg; | 
 |   } | 
 |  | 
 |   mmio_region_write32(gpio->base_addr, GPIO_DIRECT_OE_REG_OFFSET, state); | 
 |  | 
 |   return kDifOk; | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_output_set_enabled(const dif_gpio_t *gpio, | 
 |                                          dif_gpio_pin_t pin, | 
 |                                          dif_toggle_t state) { | 
 |   return gpio_masked_bit_write(gpio, GPIO_MASKED_OE_LOWER_REG_OFFSET, | 
 |                                GPIO_MASKED_OE_UPPER_REG_OFFSET, pin, state); | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_output_set_enabled_masked(const dif_gpio_t *gpio, | 
 |                                                 dif_gpio_mask_t mask, | 
 |                                                 dif_gpio_state_t state) { | 
 |   return gpio_masked_write(gpio, GPIO_MASKED_OE_LOWER_REG_OFFSET, | 
 |                            GPIO_MASKED_OE_UPPER_REG_OFFSET, mask, state); | 
 | } | 
 |  | 
 | dif_result_t dif_gpio_input_noise_filter_set_enabled(const dif_gpio_t *gpio, | 
 |                                                      dif_gpio_mask_t mask, | 
 |                                                      dif_toggle_t state) { | 
 |   if (gpio == NULL) { | 
 |     return kDifBadArg; | 
 |   } | 
 |  | 
 |   switch (state) { | 
 |     case kDifToggleEnabled: | 
 |       mmio_region_nonatomic_set_mask32( | 
 |           gpio->base_addr, GPIO_CTRL_EN_INPUT_FILTER_REG_OFFSET, mask, 0); | 
 |       break; | 
 |     case kDifToggleDisabled: | 
 |       mmio_region_nonatomic_clear_mask32( | 
 |           gpio->base_addr, GPIO_CTRL_EN_INPUT_FILTER_REG_OFFSET, mask, 0); | 
 |       break; | 
 |     default: | 
 |       return kDifBadArg; | 
 |   } | 
 |  | 
 |   return kDifOk; | 
 | } |