text: // 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_i2c.h"
  
  #include "sw/device/lib/base/bitfield.h"
  #include "i2c_regs.h"  // Generated
  
  /**
   * Performs a 32-bit integer unsigned division, rounding up. The bottom
   * 16 bits of the result are then returned.
   *
   * As usual, a divisor of 0 is still Undefined Behavior.
   */
  static uint16_t round_up_divide(uint32_t a, uint32_t b) {
    if (a == 0) {
      return 0;
    }
  
    return ((a - 1) / b) + 1;
  }
  
  /**
   * Computes default timing parameters for a particular I2C speed, given the
   * clock period, in nanoseconds.
   *
   * Returns an unspecified value for an invalid speed.
   */
  static dif_i2c_timing_params_t default_timing_for_speed(
      dif_i2c_speed_t speed, uint32_t clock_period_nanos) {
    // NOTE: All constants below are lifted from Table 10 of the I2C spec.
    // All literal values are given in nanoseconds; we don't bother putting
    // these into constants since they are not used anywhere else.
    switch (speed) {
      case kDifI2cSpeedStandard:
        return (dif_i2c_timing_params_t){
            .scl_time_high = round_up_divide(4000, clock_period_nanos),
            .scl_time_low = round_up_divide(4700, clock_period_nanos),
            .start_signal_setup_time = round_up_divide(4700, clock_period_nanos),
            .start_signal_hold_time = round_up_divide(4000, clock_period_nanos),
            .data_signal_setup_time = round_up_divide(250, clock_period_nanos),
            .data_signal_hold_time = 0,
            .stop_signal_setup_time = round_up_divide(4000, clock_period_nanos),
            .stop_signal_hold_time = round_up_divide(4700, clock_period_nanos),
        };
      case kDifI2cSpeedFast:
        return (dif_i2c_timing_params_t){
            .scl_time_high = round_up_divide(600, clock_period_nanos),
            .scl_time_low = round_up_divide(1300, clock_period_nanos),
            .start_signal_setup_time = round_up_divide(600, clock_period_nanos),
            .start_signal_hold_time = round_up_divide(600, clock_period_nanos),
            .data_signal_setup_time = round_up_divide(100, clock_period_nanos),
            .data_signal_hold_time = 0,
            .stop_signal_setup_time = round_up_divide(600, clock_period_nanos),
            .stop_signal_hold_time = round_up_divide(1300, clock_period_nanos),
        };
      case kDifI2cSpeedFastPlus:
        return (dif_i2c_timing_params_t){
            .scl_time_high = round_up_divide(260, clock_period_nanos),
            .scl_time_low = round_up_divide(500, clock_period_nanos),
            .start_signal_setup_time = round_up_divide(260, clock_period_nanos),
            .start_signal_hold_time = round_up_divide(260, clock_period_nanos),
            .data_signal_setup_time = round_up_divide(50, clock_period_nanos),
            .data_signal_hold_time = 0,
            .stop_signal_setup_time = round_up_divide(260, clock_period_nanos),
            .stop_signal_hold_time = round_up_divide(500, clock_period_nanos),
        };
      default:
        return (dif_i2c_timing_params_t){0};
    }
  }
  
  static const uint32_t kNanosPerKBaud = 1000000;  // One million.
  
  dif_i2c_result_t dif_i2c_compute_timing(const dif_i2c_timing_config_t *config,
                                          dif_i2c_timing_params_t *out) {
    if (config == NULL || out == NULL) {
      return kDifI2cBadArg;
    }
    uint32_t lowest_target_device_speed_khz;
    switch (config->lowest_target_device_speed) {
      case kDifI2cSpeedStandard:
        lowest_target_device_speed_khz = 100;
        break;
      case kDifI2cSpeedFast:
        lowest_target_device_speed_khz = 400;
        break;
      case kDifI2cSpeedFastPlus:
        lowest_target_device_speed_khz = 1000;
        break;
      default:
        return kDifI2cBadArg;
    }
  
    // This code follows the algorithm given in
    // https://docs.opentitan.org/hw/ip/i2c/doc/index.html#initialization
  
    *out = default_timing_for_speed(config->lowest_target_device_speed,
                                    config->clock_period_nanos);
  
    out->rise_time =
        round_up_divide(config->sda_rise_nanos, config->clock_period_nanos);
    out->fall_time =
        round_up_divide(config->sda_fall_nanos, config->clock_period_nanos);
  
    uint32_t scl_period_nanos = config->scl_period_nanos;
    uint32_t slowest_scl_period_nanos =
        kNanosPerKBaud / lowest_target_device_speed_khz;
    if (scl_period_nanos < slowest_scl_period_nanos) {
      scl_period_nanos = slowest_scl_period_nanos;
    }
    uint16_t scl_period_cycles =
        round_up_divide(scl_period_nanos, config->clock_period_nanos);
  
    // Lengthen the SCL high period to accomodate the desired SCL period.
    uint16_t lengthened_high_time =
        scl_period_cycles - out->scl_time_low - out->rise_time - out->fall_time;
    if (lengthened_high_time > out->scl_time_high) {
      out->scl_time_high = lengthened_high_time;
    }
  
    return kDifI2cOk;
  }
  
  /**
   * Writes all ten timing params in `timing` into the register in `i2c`.
   */
  static void write_timing_params(const dif_i2c_t *i2c,
                                  const dif_i2c_timing_params_t *timing) {
    uint32_t timing0 = 0;
    timing0 = bitfield_set_field32(timing0, (bitfield_field32_t){
                                                .mask = I2C_TIMING0_THIGH_MASK,
                                                .index = I2C_TIMING0_THIGH_OFFSET,
                                                .value = timing->scl_time_high,
                                            });
    timing0 = bitfield_set_field32(timing0, (bitfield_field32_t){
                                                .mask = I2C_TIMING0_TLOW_MASK,
                                                .index = I2C_TIMING0_TLOW_OFFSET,
                                                .value = timing->scl_time_low,
                                            });
    mmio_region_write32(i2c->base_addr, I2C_TIMING0_REG_OFFSET, timing0);
  
    uint32_t timing1 = 0;
    timing1 = bitfield_set_field32(timing1, (bitfield_field32_t){
                                                .mask = I2C_TIMING1_T_R_MASK,
                                                .index = I2C_TIMING1_T_R_OFFSET,
                                                .value = timing->rise_time,
                                            });
    timing1 = bitfield_set_field32(timing1, (bitfield_field32_t){
                                                .mask = I2C_TIMING1_T_F_MASK,
                                                .index = I2C_TIMING1_T_F_OFFSET,
                                                .value = timing->fall_time,
                                            });
    mmio_region_write32(i2c->base_addr, I2C_TIMING1_REG_OFFSET, timing1);
  
    uint32_t timing2 = 0;
    timing2 = bitfield_set_field32(timing2,
                                   (bitfield_field32_t){
                                       .mask = I2C_TIMING2_TSU_STA_MASK,
                                       .index = I2C_TIMING2_TSU_STA_OFFSET,
                                       .value = timing->start_signal_setup_time,
                                   });
    timing2 =
        bitfield_set_field32(timing2, (bitfield_field32_t){
                                          .mask = I2C_TIMING2_THD_STA_MASK,
                                          .index = I2C_TIMING2_THD_STA_OFFSET,
                                          .value = timing->start_signal_hold_time,
                                      });
    mmio_region_write32(i2c->base_addr, I2C_TIMING2_REG_OFFSET, timing2);
  
    uint32_t timing3 = 0;
    timing3 =
        bitfield_set_field32(timing3, (bitfield_field32_t){
                                          .mask = I2C_TIMING3_TSU_DAT_MASK,
                                          .index = I2C_TIMING3_TSU_DAT_OFFSET,
                                          .value = timing->data_signal_setup_time,
                                      });
    timing3 =
        bitfield_set_field32(timing3, (bitfield_field32_t){
                                          .mask = I2C_TIMING3_THD_DAT_MASK,
                                          .index = I2C_TIMING3_THD_DAT_OFFSET,
                                          .value = timing->data_signal_hold_time,
                                      });
    mmio_region_write32(i2c->base_addr, I2C_TIMING3_REG_OFFSET, timing3);
  
    uint32_t timing4 = 0;
    timing4 =
        bitfield_set_field32(timing4, (bitfield_field32_t){
                                          .mask = I2C_TIMING4_TSU_STO_MASK,
                                          .index = I2C_TIMING4_TSU_STO_OFFSET,
                                          .value = timing->stop_signal_setup_time,
                                      });
    timing4 =
        bitfield_set_field32(timing4, (bitfield_field32_t){
                                          .mask = I2C_TIMING4_T_BUF_MASK,
                                          .index = I2C_TIMING4_T_BUF_OFFSET,
                                          .value = timing->stop_signal_hold_time,
                                      });
    mmio_region_write32(i2c->base_addr, I2C_TIMING4_REG_OFFSET, timing4);
  }
  
  dif_i2c_result_t dif_i2c_init(mmio_region_t base_addr,
                                const dif_i2c_timing_params_t *timing,
                                dif_i2c_t *i2c) {
    if (timing == NULL || i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    i2c->base_addr = base_addr;
  
    // Turn the device off, before resetting it.
    dif_i2c_result_t error;
    error = dif_i2c_host_set_enabled(i2c, kDifI2cDisabled);
    if (error != kDifI2cOk) {
      return error;
    }
  
    // Clear + disable all interrupts. We do this directly, rather than using the
    // actual DIF functions, to perform both in single store operations.
    mmio_region_write32(i2c->base_addr, I2C_INTR_STATE_REG_OFFSET, UINT32_MAX);
    mmio_region_write32(i2c->base_addr, I2C_INTR_ENABLE_REG_OFFSET, 0);
  
    // Reset all FIFO state.
    error = dif_i2c_reset_rx_fifo(i2c);
    if (error != kDifI2cOk) {
      return error;
    }
    error = dif_i2c_reset_fmt_fifo(i2c);
    if (error != kDifI2cOk) {
      return error;
    }
    error = dif_i2c_set_watermarks(i2c, kDifI2cLevel1Byte, kDifI2cLevel1Byte);
    if (error != kDifI2cOk) {
      return error;
    }
  
    write_timing_params(i2c, timing);
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_reset_rx_fifo(const dif_i2c_t *i2c) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    mmio_region_nonatomic_set_bit32(i2c->base_addr, I2C_FIFO_CTRL_REG_OFFSET,
                                    I2C_FIFO_CTRL_RXRST);
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_reset_fmt_fifo(const dif_i2c_t *i2c) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    mmio_region_nonatomic_set_bit32(i2c->base_addr, I2C_FIFO_CTRL_REG_OFFSET,
                                    I2C_FIFO_CTRL_FMTRST);
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_set_watermarks(const dif_i2c_t *i2c,
                                          dif_i2c_level_t rx_level,
                                          dif_i2c_level_t fmt_level) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    ptrdiff_t rx_level_value;
    switch (rx_level) {
      case kDifI2cLevel1Byte:
        rx_level_value = I2C_FIFO_CTRL_RXILVL_RXLVL1;
        break;
      case kDifI2cLevel4Byte:
        rx_level_value = I2C_FIFO_CTRL_RXILVL_RXLVL4;
        break;
      case kDifI2cLevel8Byte:
        rx_level_value = I2C_FIFO_CTRL_RXILVL_RXLVL8;
        break;
      case kDifI2cLevel16Byte:
        rx_level_value = I2C_FIFO_CTRL_RXILVL_RXLVL16;
        break;
      case kDifI2cLevel30Byte:
        rx_level_value = I2C_FIFO_CTRL_RXILVL_RXLVL30;
        break;
      default:
        return kDifI2cBadArg;
    }
  
    ptrdiff_t fmt_level_value;
    switch (fmt_level) {
      case kDifI2cLevel1Byte:
        fmt_level_value = I2C_FIFO_CTRL_FMTILVL_FMTLVL1;
        break;
      case kDifI2cLevel4Byte:
        fmt_level_value = I2C_FIFO_CTRL_FMTILVL_FMTLVL4;
        break;
      case kDifI2cLevel8Byte:
        fmt_level_value = I2C_FIFO_CTRL_FMTILVL_FMTLVL8;
        break;
      case kDifI2cLevel16Byte:
        fmt_level_value = I2C_FIFO_CTRL_FMTILVL_FMTLVL16;
        break;
      default:
        return kDifI2cBadArg;
    }
  
    uint32_t ctrl_value =
        mmio_region_read32(i2c->base_addr, I2C_FIFO_CTRL_REG_OFFSET);
    ctrl_value = bitfield_set_field32(
        ctrl_value, (bitfield_field32_t){.mask = I2C_FIFO_CTRL_RXILVL_MASK,
                                         .index = I2C_FIFO_CTRL_RXILVL_OFFSET,
                                         .value = rx_level_value});
    ctrl_value = bitfield_set_field32(ctrl_value,
                                      (bitfield_field32_t){
                                          .mask = I2C_FIFO_CTRL_FMTILVL_MASK,
                                          .index = I2C_FIFO_CTRL_FMTILVL_OFFSET,
                                          .value = fmt_level_value,
                                      });
    mmio_region_write32(i2c->base_addr, I2C_FIFO_CTRL_REG_OFFSET, ctrl_value);
  
    return kDifI2cOk;
  }
  
  static dif_i2c_result_t irq_bit_for_type(dif_i2c_irq_type_t type,
                                           uint32_t *bit_index) {
    switch (type) {
      case kDifI2cIrqTypeFmtWatermarkUnderflow:
        *bit_index = I2C_INTR_COMMON_FMT_WATERMARK;
        break;
      case kDifI2cIrqTypeRxWatermarkOverflow:
        *bit_index = I2C_INTR_COMMON_RX_WATERMARK;
        break;
      case kDifI2cIrqTypeFmtFifoOverflow:
        *bit_index = I2C_INTR_COMMON_FMT_OVERFLOW;
        break;
      case kDifI2cIrqTypeRxFifoOverflow:
        *bit_index = I2C_INTR_COMMON_RX_OVERFLOW;
        break;
      case kDifI2cIrqTypeNak:
        *bit_index = I2C_INTR_COMMON_NAK;
        break;
      case kDifI2cIrqTypeSclInterference:
        *bit_index = I2C_INTR_COMMON_SCL_INTERFERENCE;
        break;
      case kDifI2cIrqTypeSdaInterference:
        *bit_index = I2C_INTR_COMMON_SDA_INTERFERENCE;
        break;
      case kDifI2cIrqTypeClockStretchTimeout:
        *bit_index = I2C_INTR_COMMON_STRETCH_TIMEOUT;
        break;
      case kDifI2cIrqTypeSdaUnstable:
        *bit_index = I2C_INTR_COMMON_SDA_UNSTABLE;
        break;
      default:
        return kDifI2cBadArg;
    }
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_irq_get(const dif_i2c_t *i2c, dif_i2c_irq_type_t type,
                                   bool *flag_out) {
    if (i2c == NULL || flag_out == NULL) {
      return kDifI2cBadArg;
    }
  
    uint32_t index;
    dif_i2c_result_t err = irq_bit_for_type(type, &index);
    if (err != kDifI2cOk) {
      return err;
    }
  
    *flag_out =
        mmio_region_get_bit32(i2c->base_addr, I2C_INTR_STATE_REG_OFFSET, index);
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_irq_clear(const dif_i2c_t *i2c,
                                     dif_i2c_irq_type_t type) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    uint32_t index;
    dif_i2c_result_t err = irq_bit_for_type(type, &index);
    if (err != kDifI2cOk) {
      return err;
    }
  
    // IRQ state registers are write-one-clear.
    mmio_region_write_only_set_bit32(i2c->base_addr, I2C_INTR_STATE_REG_OFFSET,
                                     index);
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_irq_set_enabled(const dif_i2c_t *i2c,
                                           dif_i2c_irq_type_t type,
                                           dif_i2c_enable_t state) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    uint32_t index;
    dif_i2c_result_t err = irq_bit_for_type(type, &index);
    if (err != kDifI2cOk) {
      return err;
    }
  
    switch (state) {
      case kDifI2cEnabled:
        mmio_region_nonatomic_set_bit32(i2c->base_addr,
                                        I2C_INTR_ENABLE_REG_OFFSET, index);
        break;
      case kDifI2cDisabled:
        mmio_region_nonatomic_clear_bit32(i2c->base_addr,
                                          I2C_INTR_ENABLE_REG_OFFSET, index);
        break;
      default:
        return kDifI2cBadArg;
    }
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_irq_force(const dif_i2c_t *i2c,
                                     dif_i2c_irq_type_t type) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    uint32_t index;
    dif_i2c_result_t err = irq_bit_for_type(type, &index);
    if (err != kDifI2cOk) {
      return err;
    }
  
    mmio_region_write_only_set_bit32(i2c->base_addr, I2C_INTR_TEST_REG_OFFSET,
                                     index);
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_host_set_enabled(const dif_i2c_t *i2c,
                                            dif_i2c_enable_t state) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    switch (state) {
      case kDifI2cEnabled:
        mmio_region_nonatomic_set_bit32(i2c->base_addr, I2C_CTRL_REG_OFFSET,
                                        I2C_CTRL_ENABLEHOST);
        break;
      case kDifI2cDisabled:
        mmio_region_nonatomic_clear_bit32(i2c->base_addr, I2C_CTRL_REG_OFFSET,
                                          I2C_CTRL_ENABLEHOST);
        break;
      default:
        return kDifI2cBadArg;
    }
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_override_set_enabled(const dif_i2c_t *i2c,
                                                dif_i2c_enable_t state) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    switch (state) {
      case kDifI2cEnabled:
        mmio_region_nonatomic_set_bit32(i2c->base_addr, I2C_OVRD_REG_OFFSET,
                                        I2C_OVRD_TXOVRDEN);
        break;
      case kDifI2cDisabled:
        mmio_region_nonatomic_clear_bit32(i2c->base_addr, I2C_OVRD_REG_OFFSET,
                                          I2C_OVRD_TXOVRDEN);
        break;
      default:
        return kDifI2cBadArg;
    }
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_override_drive_pins(const dif_i2c_t *i2c, bool scl,
                                               bool sda) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    uint32_t override_val =
        mmio_region_read32(i2c->base_addr, I2C_OVRD_REG_OFFSET);
    override_val = bitfield_set_field32(
        override_val, (bitfield_field32_t){
                          .mask = 1, .index = I2C_OVRD_SCLVAL, .value = scl,
                      });
    override_val = bitfield_set_field32(
        override_val, (bitfield_field32_t){
                          .mask = 1, .index = I2C_OVRD_SDAVAL, .value = sda,
                      });
    mmio_region_write32(i2c->base_addr, I2C_OVRD_REG_OFFSET, override_val);
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_override_sample_pins(const dif_i2c_t *i2c,
                                                uint16_t *scl_samples,
                                                uint16_t *sda_samples) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    uint32_t samples = mmio_region_read32(i2c->base_addr, I2C_VAL_REG_OFFSET);
    if (scl_samples != NULL) {
      *scl_samples = bitfield_get_field32(
          samples,
          (bitfield_field32_t){
              .mask = I2C_VAL_SCL_RX_MASK, .index = I2C_VAL_SCL_RX_OFFSET,
          });
    }
  
    if (sda_samples != NULL) {
      *sda_samples = bitfield_get_field32(
          samples,
          (bitfield_field32_t){
              .mask = I2C_VAL_SDA_RX_MASK, .index = I2C_VAL_SDA_RX_OFFSET,
          });
    }
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_get_fifo_levels(const dif_i2c_t *i2c,
                                           uint8_t *fmt_fifo_level,
                                           uint8_t *rx_fifo_level) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    uint32_t values =
        mmio_region_read32(i2c->base_addr, I2C_FIFO_STATUS_REG_OFFSET);
    if (fmt_fifo_level != NULL) {
      *fmt_fifo_level =
          bitfield_get_field32(values, (bitfield_field32_t){
                                           .mask = I2C_FIFO_STATUS_FMTLVL_MASK,
                                           .index = I2C_FIFO_STATUS_FMTLVL_OFFSET,
                                       });
    }
    if (rx_fifo_level != NULL) {
      *rx_fifo_level =
          bitfield_get_field32(values, (bitfield_field32_t){
                                           .mask = I2C_FIFO_STATUS_RXLVL_MASK,
                                           .index = I2C_FIFO_STATUS_RXLVL_OFFSET,
                                       });
    }
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_read_byte(const dif_i2c_t *i2c, uint8_t *byte) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    uint32_t values = mmio_region_read32(i2c->base_addr, I2C_RDATA_REG_OFFSET);
    if (byte != NULL) {
      *byte = bitfield_get_field32(values, (bitfield_field32_t){
                                               .mask = I2C_RDATA_RDATA_MASK,
                                               .index = I2C_RDATA_RDATA_OFFSET,
                                           });
    }
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_write_byte_raw(const dif_i2c_t *i2c, uint8_t byte,
                                          dif_i2c_fmt_flags_t flags) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
    // Validate that "write only" flags and "read only" flags are not set
    // simulataneously.
    bool has_write_flags = flags.start || flags.stop || flags.supress_nak_irq;
    bool has_read_flags = flags.read || flags.read_cont;
    if (has_write_flags && has_read_flags) {
      return kDifI2cBadArg;
    }
    // Also, read_cont requires read.
    if (flags.read_cont && !flags.read) {
      return kDifI2cBadArg;
    }
  
    uint32_t fmt_byte = 0;
    fmt_byte = bitfield_set_field32(fmt_byte, (bitfield_field32_t){
                                                  .mask = I2C_FDATA_FBYTE_MASK,
                                                  .index = I2C_FDATA_FBYTE_OFFSET,
                                                  .value = byte,
                                              });
    fmt_byte = bitfield_set_field32(
        fmt_byte, (bitfield_field32_t){
                      .mask = 1, .index = I2C_FDATA_START, .value = flags.start,
                  });
    fmt_byte = bitfield_set_field32(
        fmt_byte, (bitfield_field32_t){
                      .mask = 1, .index = I2C_FDATA_STOP, .value = flags.stop,
                  });
    fmt_byte = bitfield_set_field32(
        fmt_byte, (bitfield_field32_t){
                      .mask = 1, .index = I2C_FDATA_READ, .value = flags.read,
                  });
    fmt_byte = bitfield_set_field32(
        fmt_byte,
        (bitfield_field32_t){
            .mask = 1, .index = I2C_FDATA_RCONT, .value = flags.read_cont,
        });
    fmt_byte = bitfield_set_field32(
        fmt_byte,
        (bitfield_field32_t){
            .mask = 1, .index = I2C_FDATA_NAKOK, .value = flags.supress_nak_irq,
        });
    mmio_region_write32(i2c->base_addr, I2C_FDATA_REG_OFFSET, fmt_byte);
  
    return kDifI2cOk;
  }
  
  dif_i2c_result_t dif_i2c_write_byte(const dif_i2c_t *i2c, uint8_t byte,
                                      dif_i2c_fmt_t code, bool supress_nak_irq) {
    if (i2c == NULL) {
      return kDifI2cBadArg;
    }
  
    // Validate that `supress_nak_irq` has not been mixed with an Rx code.
    if (supress_nak_irq) {
      switch (code) {
        case kDifI2cFmtRx:
        case kDifI2cFmtRxContinue:
        case kDifI2cFmtRxStop:
          return kDifI2cBadArg;
        default:
          break;
      }
    }
  
    // Convert the format code into flags.
    dif_i2c_fmt_flags_t flags = {.supress_nak_irq = supress_nak_irq};
    switch (code) {
      case kDifI2cFmtStart:
        flags.start = true;
        break;
      case kDifI2cFmtTx:
        break;
      case kDifI2cFmtTxStop:
        flags.stop = true;
        break;
      case kDifI2cFmtRx:
        flags.read = true;
        break;
      case kDifI2cFmtRxContinue:
        flags.read = true;
        flags.read_cont = true;
        break;
      case kDifI2cFmtRxStop:
        flags.read = true;
        flags.stop = true;
      default:
        return kDifI2cBadArg;
    }
  
    return dif_i2c_write_byte_raw(i2c, byte, flags);
  }
  