blob: 188d8da4960243c4878d9af6ea8b533646c2f07f [file] [log] [blame]
// 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.
/**
* 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.
*/
DIF_WARN_UNUSED_RESULT
static dif_gpio_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 kDifGpioBadArg;
}
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->params.base_addr, reg_lower_offset,
(mask_lower_half << 16) | (val & 0x0000FFFFu));
}
if (mask_upper_half != 0) {
mmio_region_write32(gpio->params.base_addr, reg_upper_offset,
mask_upper_half | ((val & 0xFFFF0000u) >> 16));
}
return kDifGpioOk;
}
/**
* 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.
*/
DIF_WARN_UNUSED_RESULT
static dif_gpio_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) {
return kDifGpioBadArg;
}
// 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->params.base_addr, offset,
(mask << 16) | (val ? mask : 0u));
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_init(dif_gpio_params_t params, dif_gpio_t *gpio) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
gpio->params = params;
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_reset(const dif_gpio_t *gpio) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
// 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->params.base_addr, GPIO_INTR_ENABLE_REG_OFFSET, 0);
mmio_region_write32(gpio->params.base_addr, GPIO_DIRECT_OE_REG_OFFSET, 0);
mmio_region_write32(gpio->params.base_addr, GPIO_DIRECT_OUT_REG_OFFSET, 0);
mmio_region_write32(gpio->params.base_addr,
GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, 0);
mmio_region_write32(gpio->params.base_addr,
GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET, 0);
mmio_region_write32(gpio->params.base_addr,
GPIO_INTR_CTRL_EN_LVLHIGH_REG_OFFSET, 0);
mmio_region_write32(gpio->params.base_addr,
GPIO_INTR_CTRL_EN_LVLLOW_REG_OFFSET, 0);
mmio_region_write32(gpio->params.base_addr,
GPIO_CTRL_EN_INPUT_FILTER_REG_OFFSET, 0);
// Also clear all pending interrupts
mmio_region_write32(gpio->params.base_addr, GPIO_INTR_STATE_REG_OFFSET,
0xFFFFFFFFu);
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_irq_is_pending(const dif_gpio_t *gpio,
dif_gpio_pin_t pin,
bool *is_pending) {
if (gpio == NULL || is_pending == NULL) {
return kDifGpioBadArg;
}
*is_pending = mmio_region_get_bit32(gpio->params.base_addr,
GPIO_INTR_STATE_REG_OFFSET, pin);
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_irq_is_pending_all(const dif_gpio_t *gpio,
dif_gpio_state_t *is_pending) {
if (gpio == NULL || is_pending == NULL) {
return kDifGpioBadArg;
}
*is_pending =
mmio_region_read32(gpio->params.base_addr, GPIO_INTR_STATE_REG_OFFSET);
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_irq_acknowledge(const dif_gpio_t *gpio,
dif_gpio_pin_t pin) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
mmio_region_write32(gpio->params.base_addr, GPIO_INTR_STATE_REG_OFFSET,
index_to_mask(pin));
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_irq_get_enabled(const dif_gpio_t *gpio,
dif_gpio_pin_t pin,
dif_gpio_toggle_t *state) {
if (gpio == NULL || state == NULL) {
return kDifGpioBadArg;
}
bool is_enabled = mmio_region_get_bit32(gpio->params.base_addr,
GPIO_INTR_ENABLE_REG_OFFSET, pin);
*state = is_enabled ? kDifGpioToggleEnabled : kDifGpioToggleDisabled;
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_irq_set_enabled(const dif_gpio_t *gpio,
dif_gpio_pin_t pin,
dif_gpio_toggle_t state) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
switch (state) {
case kDifGpioToggleEnabled:
mmio_region_nonatomic_set_bit32(gpio->params.base_addr,
GPIO_INTR_ENABLE_REG_OFFSET, pin);
break;
case kDifGpioToggleDisabled:
mmio_region_nonatomic_clear_bit32(gpio->params.base_addr,
GPIO_INTR_ENABLE_REG_OFFSET, pin);
break;
default:
return kDifGpioBadArg;
}
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_irq_set_enabled_masked(const dif_gpio_t *gpio,
dif_gpio_mask_t mask,
dif_gpio_toggle_t state) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
switch (state) {
case kDifGpioToggleEnabled:
mmio_region_nonatomic_set_mask32(gpio->params.base_addr,
GPIO_INTR_ENABLE_REG_OFFSET, mask, 0);
break;
case kDifGpioToggleDisabled:
mmio_region_nonatomic_clear_mask32(gpio->params.base_addr,
GPIO_INTR_ENABLE_REG_OFFSET, mask, 0);
break;
default:
return kDifGpioBadArg;
}
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_irq_force(const dif_gpio_t *gpio,
dif_gpio_pin_t pin) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
mmio_region_write32(gpio->params.base_addr, GPIO_INTR_TEST_REG_OFFSET,
index_to_mask(pin));
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_irq_disable_all(const dif_gpio_t *gpio,
dif_gpio_state_t *snapshot) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
if (snapshot != NULL) {
*snapshot =
mmio_region_read32(gpio->params.base_addr, GPIO_INTR_ENABLE_REG_OFFSET);
}
mmio_region_write32(gpio->params.base_addr, GPIO_INTR_ENABLE_REG_OFFSET, 0);
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_irq_restore_all(const dif_gpio_t *gpio,
const dif_gpio_state_t *snapshot) {
if (gpio == NULL || snapshot == NULL) {
return kDifGpioBadArg;
}
mmio_region_write32(gpio->params.base_addr, GPIO_INTR_ENABLE_REG_OFFSET,
*snapshot);
return kDifGpioOk;
}
dif_gpio_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 kDifGpioBadArg;
}
// Disable all interrupt triggers for the given mask.
mmio_region_nonatomic_clear_mask32(
gpio->params.base_addr, GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, mask, 0);
mmio_region_nonatomic_clear_mask32(
gpio->params.base_addr, GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET, mask, 0);
mmio_region_nonatomic_clear_mask32(
gpio->params.base_addr, GPIO_INTR_CTRL_EN_LVLHIGH_REG_OFFSET, mask, 0);
mmio_region_nonatomic_clear_mask32(
gpio->params.base_addr, GPIO_INTR_CTRL_EN_LVLLOW_REG_OFFSET, mask, 0);
switch (trigger) {
case kDifGpioIrqTriggerEdgeRising:
mmio_region_nonatomic_set_mask32(
gpio->params.base_addr, GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, mask, 0);
break;
case kDifGpioIrqTriggerEdgeFalling:
mmio_region_nonatomic_set_mask32(gpio->params.base_addr,
GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET,
mask, 0);
break;
case kDifGpioIrqTriggerLevelLow:
mmio_region_nonatomic_set_mask32(
gpio->params.base_addr, GPIO_INTR_CTRL_EN_LVLLOW_REG_OFFSET, mask, 0);
break;
case kDifGpioIrqTriggerLevelHigh:
mmio_region_nonatomic_set_mask32(gpio->params.base_addr,
GPIO_INTR_CTRL_EN_LVLHIGH_REG_OFFSET,
mask, 0);
break;
case kDifGpioIrqTriggerEdgeRisingFalling:
mmio_region_nonatomic_set_mask32(
gpio->params.base_addr, GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, mask, 0);
mmio_region_nonatomic_set_mask32(gpio->params.base_addr,
GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET,
mask, 0);
break;
case kDifGpioIrqTriggerEdgeRisingLevelLow:
mmio_region_nonatomic_set_mask32(
gpio->params.base_addr, GPIO_INTR_CTRL_EN_RISING_REG_OFFSET, mask, 0);
mmio_region_nonatomic_set_mask32(
gpio->params.base_addr, GPIO_INTR_CTRL_EN_LVLLOW_REG_OFFSET, mask, 0);
break;
case kDifGpioIrqTriggerEdgeFallingLevelHigh:
mmio_region_nonatomic_set_mask32(gpio->params.base_addr,
GPIO_INTR_CTRL_EN_FALLING_REG_OFFSET,
mask, 0);
mmio_region_nonatomic_set_mask32(gpio->params.base_addr,
GPIO_INTR_CTRL_EN_LVLHIGH_REG_OFFSET,
mask, 0);
break;
default:
return kDifGpioError;
}
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_read_all(const dif_gpio_t *gpio,
dif_gpio_state_t *state) {
if (gpio == NULL || state == NULL) {
return kDifGpioBadArg;
}
*state = mmio_region_read32(gpio->params.base_addr, GPIO_DATA_IN_REG_OFFSET);
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_read(const dif_gpio_t *gpio, dif_gpio_pin_t pin,
bool *state) {
if (gpio == NULL || state == NULL) {
return kDifGpioBadArg;
}
*state = mmio_region_get_bit32(gpio->params.base_addr,
GPIO_DATA_IN_REG_OFFSET, pin);
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_write_all(const dif_gpio_t *gpio,
dif_gpio_state_t state) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
mmio_region_write32(gpio->params.base_addr, GPIO_DIRECT_OUT_REG_OFFSET,
state);
return kDifGpioOk;
}
dif_gpio_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_gpio_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_gpio_result_t dif_gpio_output_set_enabled_all(const dif_gpio_t *gpio,
dif_gpio_state_t state) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
mmio_region_write32(gpio->params.base_addr, GPIO_DIRECT_OE_REG_OFFSET, state);
return kDifGpioOk;
}
dif_gpio_result_t dif_gpio_output_set_enabled(const dif_gpio_t *gpio,
dif_gpio_pin_t pin,
dif_gpio_toggle_t state) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
return gpio_masked_bit_write(gpio, GPIO_MASKED_OE_LOWER_REG_OFFSET,
GPIO_MASKED_OE_UPPER_REG_OFFSET, pin, state);
return kDifGpioOk;
}
dif_gpio_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_gpio_result_t dif_gpio_input_noise_filter_set_enabled(
const dif_gpio_t *gpio, dif_gpio_mask_t mask, dif_gpio_toggle_t state) {
if (gpio == NULL) {
return kDifGpioBadArg;
}
switch (state) {
case kDifGpioToggleEnabled:
mmio_region_nonatomic_set_mask32(gpio->params.base_addr,
GPIO_CTRL_EN_INPUT_FILTER_REG_OFFSET,
mask, 0);
break;
case kDifGpioToggleDisabled:
mmio_region_nonatomic_clear_mask32(gpio->params.base_addr,
GPIO_CTRL_EN_INPUT_FILTER_REG_OFFSET,
mask, 0);
break;
default:
return kDifGpioBadArg;
}
return kDifGpioOk;
}