# `brawl:device/i2c`

## Overview

The **`brawl:device/i2c`** interface defines a portable, minimal **I2C (Wire)** API for WebAssembly (WASM) modules. It provides a consistent way to control and monitor I2C buses across diverse embedded targets - ranging from microcontrollers to emulators - while remaining fully compatible with the WASM MVP specification.

Each function follows a simple convention: it returns a signed 32-bit integer, where negative values represent standardized error codes and non-negative values represent valid results or success indicators. This design keeps the ABI deterministic, low-overhead, and portable between environments, enabling WASM applications to interact directly with physical or virtual I2C devices without callbacks, threads, or host-specific extensions.

## Implementation Notes

This section outlines the expected behavior and conventions used by the `brawl:device/i2c` interface. It provides guidance for both host implementers and guest developers to ensure consistent behavior across WASM targets.

#### I2C Bus Enumeration

`i2cCount()` returns the total number of I2C buses available on the target. `i2cList()` populates a memory buffer with a list of available bus identifiers (`uint8_t`) in the host environment’s canonical order.  Each bus identifier can be passed directly to `i2cOpen()` to obtain a handle for further operations.

#### Open and Configure

Before utilizing an I2C bus, always call `i2cCapabilities()` to verify that it supports the desired modes. Use `i2cOpen()` to reserve the I2C bus and obtain a handle; this handle is passed to all subsequent operations. When the I2C bus is no longer needed, release it using `i2cClose()`. I2C buses remain inactive until configured with calls to `i2cSetFrequency()`, `i2cSetTimeout()` and `i2cSet10Bit()` - to define the I2C bus behavior and electrical characteristics.

#### Software I2C (bit-banged SDA/SCL)

I2C can be implemented on any two GPIO pins by emulating open-drain signalling: the host drives lines low or releases them to pull-ups. Clock-stretching and 10-bit addressing are supported only if the host declares them in capabilities. Existing read/write and write-then-read calls operate transparently on the software bus handle. Pull-ups may be external or, if available, enabled by the host internally.

To detect if the host supports a software implementation, call `i2cModuleCapabilities()` check for the `I2C_CAPS_SW_PORT` capability, a I2C bus can then be created using the `i2cOpenOnPins()` function.

#### Error Codes

All `brawl:device/i2c` functions return a signed 32-bit integer (`int32_t`), which encodes both result and error information:

| Value      |                                                                          |
|------------|--------------------------------------------------------------------------|
| `ret < 0`  | an error occurred.                                                       |
| `ret == 0` | operation completed successfully (or no value to return).                |
| `ret > 0`  | operation succeeded and returned a value (e.g., I2C bus data or handle). |

All memory access operations are bounds-checked by the host; going outside will return a `I2C_ERROR_MEMORY_OOB` error.

## Constants

#### I2C Errors

All functions return a signed 32-bit integer (`int32_t`).  If the value is **negative**, it represents an error code from the table below:

| Constant                   |                                                                          |
|----------------------------|--------------------------------------------------------------------------|
| `I2C_ERROR_BUSY`           | The I2C bus is already in use or reserved by another handle.             |
| `I2C_ERROR_UNSUPPORTED`    | The requested operation or capability is not supported on this I2C bus.  |
| `I2C_ERROR_TIMEOUT`        | The operation did not complete within the expected time.                 |
| `I2C_ERROR_INVALID_HANDLE` | The handle provided was invalid for the operation requested.             |
| `I2C_ERROR_INVALID_ARG`    | The argument provided was invalid for the operation requested.           |
| `I2C_ERROR_MEMORY_OOB`     | The requested operation would result in a MEMORY_OOB Error.              |
| `I2C_ERROR_NACK`           | The operation resulted in the address or data being NACK'd.              |
| `I2C_ERROR_ARB_LOST`       | The operation arbitation was lost.                                       |
| `I2C_ERROR_BUS_ERROR`      | The operation incurred an illegal START/STOP or line error.              |

A return value **`>= 0`** indicates success - either `0` for non-returning operations or a positive value for operations that produce a result.

#### I2C Capabilities

Each I2C bus can support one or more capabilities, reported by `i2cModuleCapabilities()` and `i2cCapabilities()`. These are bit-flags and may be combined using bitwise OR.

| Constant (Flag)          |                                                     |
|--------------------------|-----------------------------------------------------|
| `I2C_CAPS_CONTROLLER`    | Supports master/controller mode.                    |
| `I2C_CAPS_DEVICE`        | Supports device/slave mode (optional, future).      |
| `I2C_CAPS_ADDR_10BIT`    | Supports 10-bit addressing.                         |
| `I2C_CAPS_GENERAL_CALL`  | Supports 0x00 general call.                         |
| `I2C_CAPS_CLOCK_STRETCH` | Supports clock stretching.                          |
| `I2C_CAPS_SMBUS`         | Supports SMBus timing/transactions.                 |
| `I2C_CAPS_SMBUS_PEC`     | Supports SMBus Packet Error Code.                   |
| `I2C_CAPS_SPEED_STD`     | Supports 100Hz.                                     |
| `I2C_CAPS_SPEED_FAST`    | Supports 400Hz.                                     |
| `I2C_CAPS_SPEED_FASTP`   | Supports 1MHz (Fast Mode Plus).                     |
| `I2C_CAPS_SPEED_HIGH`    | Supports 3.4MHz (High Speed).                       |
| `I2C_CAPS_DMA`           | Supports DMA or large-buffer transfers.             |
| `I2C_CAPS_RECOVER_BUS`   | Can attempt bus recovery (toggle SCL etc).          |
| `I2C_CAPS_SW_PORT`       | Supports creating a I2C bus on arbitrary GPIO pins. |

Combine capability flags with the bitwise OR operator (`|`) to describe an I2C bus that supports multiple features.

## Application Programmer Interface (API)

```
//--------------------------------------------------------------------------
// brawl:device/i2c

// I2C errors
enum I2C_ERROR {
                        // >= 0              no error
  I2C_ERROR_BUSY           = -1,          // bus/controller busy
  I2C_ERROR_UNSUPPORTED    = -2,          // feature not supported
  I2C_ERROR_TIMEOUT        = -3,          // transfer timed out
  I2C_ERROR_INVALID_HANDLE = -4,          // invalid handle
  I2C_ERROR_INVALID_ARG    = -5,          // invalid argument
  I2C_ERROR_MEMORY_OOB     = -6,          // memory OOB
  I2C_ERROR_NACK           = -7,          // address or data NACKed
  I2C_ERROR_ARB_LOST       = -8,          // arbitration lost
  I2C_ERROR_BUS_ERROR      = -9           // illegal START/STOP or line error
};

// I2C capabilities (flags)
enum I2C_CAPS {
  I2C_CAPS_CONTROLLER     = (1u << 0),   // master role
  I2C_CAPS_DEVICE         = (1u << 1),   // (optional future) target/slave role
  I2C_CAPS_ADDR_10BIT     = (1u << 2),   // supports 10-bit addressing
  I2C_CAPS_GENERAL_CALL   = (1u << 3),   // supports 0x00 general call
  I2C_CAPS_CLOCK_STRETCH  = (1u << 4),   // handles clock stretching
  I2C_CAPS_SMBUS          = (1u << 5),   // SMBus timing/transactions
  I2C_CAPS_SMBUS_PEC      = (1u << 6),   // SMBus Packet Error Code
  I2C_CAPS_SPEED_STD      = (1u << 7),   // 100 kHz
  I2C_CAPS_SPEED_FAST     = (1u << 8),   // 400 kHz
  I2C_CAPS_SPEED_FASTP    = (1u << 9),   // 1 MHz (Fast Mode Plus)
  I2C_CAPS_SPEED_HIGH     = (1u << 10),  // 3.4 MHz (High Speed)
  I2C_CAPS_DMA            = (1u << 11),  // large/DMA-friendly transfers
  I2C_CAPS_RECOVER_BUS    = (1u << 12),  // can attempt bus recovery (toggle SCL, etc.)
  I2C_CAPS_SW_PORT        = (1u << 13)   // supports creating a I2C bus on an arbitrary GPIO pin
};

int32_t // I2C_ERROR|I2C_CAPS
i2cModuleCapabilities(void);
```

#### Discovery

```
int32_t // I2C_ERROR|int32_t
i2cCount(void);

int32_t // I2C_ERROR|int32_t
i2cList(int32_t mem_ptr, int32_t max_items);

int32_t // I2C_ERROR|I2C_CAPS
i2cCapabilities(int32_t i2cBus);
```

#### Open and Configure

```
int32_t // I2C_ERROR|int32_t
i2cOpen(int32_t i2cBus);

int32_t // I2C_ERROR|int32_t
i2cOpenOnPins(int32_t sda_pin, int32_t scl_pin);

int32_t // I2C_ERROR|void
i2cSetFrequency(int32_t handle, int32_t hz);

int32_t // I2C_ERROR|void
i2cSetTimeout(int32_t handle, int32_t timeout_us);

int32_t // I2C_ERROR|void
i2cSet10Bit(int32_t handle, int32_t enable);

int32_t // I2C_ERROR|void
i2cClose(int32_t handle);
```

#### Transfers

```
int32_t // I2C_ERROR|void
i2cWrite(int32_t handle, int32_t addr, int32_t tx_ptr, int32_t len, int32_t stop, int32_t timeout_us);

int32_t // I2C_ERROR|void
i2cRead(int32_t handle, int32_t addr, int32_t rx_ptr, int32_t len, int32_t stop, int32_t timeout_us);

int32_t // I2C_ERROR|void
i2cWriteRead(int32_t handle, int32_t addr, int32_t tx_ptr, int32_t tx_len, int32_rx_ptr, int32_t rx_len, int32_t timeout_us);
```

#### Utilities

```
int32_t // I2C_ERROR|void
i2cScan(int32_t handle, int32_t dst_ptr, int32_t max_items);

int32_t // I2C_ERROR|void
i2cRecoverBus(int32_t handle);
```

## Example - WHOAMI on device 0x1C (C)

```
#include <stdint.h>
#include <stdio.h>

#include "brawl-device-i2c.h"

static void gpio_err(int32_t rc, const char *what) {
    if (rc < 0) fprintf(stderr, "%s failed: %d\n", what, rc);
}

int main(void) {

    int32_t i2c_h, rc, n;
    uint8_t buses[8];
    uint8_t reg, val;

    // enumerate i2c buses (WASM MVP: caller-allocated buffer)
    n = i2cCount();
    if (n < 0) { gpio_err(rc, "i2cCount"); return 1; }
    if (n > 8) n = 8;

    rc = i2cList((int32_t)(uintptr_t)buses, n);
    if (rc < 0) { gpio_err(rc, "i2cList"); return 1; }

    // do we have any I2C buses?
    if (rc > 0) {

        // open first available I2C bus
        i2c_h = i2cOpen(buses[0]);
        if (i2c_h < 0) { gpio_err(rc, "i2cOpen"); return 1; }

        // configure i2c bus for 400kHz and 7-bit
        rc = i2cSetFrequency(h, 400000);
        if (rc < 0) { gpio_err(rc, "i2cSetFrequency"); i2cClose(i2c_h); return 1; }
        rc = i2cSet10Bit(h, false);
        if (rc < 0) { gpio_err(rc, "i2cSet10Bit"); i2cClose(i2c_h); return 1; }

        // configure per-transfer timeout default
        rc = i2cSetTimeout(h, 2000);
        if (rc < 0) { gpio_err(rc, "i2cSetTimeout"); i2cClose(i2c_h); return 1; }

        // read WHOAMI register (0x0F) from device 0x1C
        reg = 0x0F;
        val = 0;
        rc = i2cWriteRead(i2c_h, 0x1C, (int32_t)(uintptr_t)&reg, 1, (int32_t)(uintptr_t)&val, 1, 2000);
        if (rc < 0) { gpio_err(rc, "i2cWriteRead"); i2cClose(i2c_h); return 1; }

        i2cClose(h);
        if (rc < 0) { gpio_err(rc, "i2cClose"); return 1; }
    }

    return 0;
}
```

#### Revision

2025-10 (draft)
