# `CircuitsFT232H.I2C`

Wires the FT232H's MPSSE engine up as an I2C master.

The FT232H has no native I2C peripheral — I2C is bit-banged via MPSSE
using two FT232H-specific tricks:

  * The `DRIVE_ZERO` opcode (0x9E) makes the chosen pins behave as true
    open-drain outputs — `0` drives low, `1` tristates. With external
    pull-ups this matches I2C's electrical model.
  * `ENABLE_3_PHASE_CLOCKING` (0x8C) adds an extra phase per bit so that
    SDA setup/hold around SCL meets the I2C spec. The effective SCK rate
    becomes 2/3 of the MPSSE divisor rate, so this module compensates by
    programming SCK = 1.5 × the requested I2C rate.

Pin assignments (fixed for v0.1):

| Pin      | Role |
| -------- | ---- |
| `ADBUS0` | SCL  |
| `ADBUS1` | SDA (out) — **must be tied externally to ADBUS2** |
| `ADBUS2` | SDA (in)  — sees what's on the SDA wire |

External pull-ups on SCL and SDA are required: 4.7 kΩ for 100 kHz,
2.2 kΩ for 400 kHz, 1 kΩ for 1 MHz.

Public API lives on `CircuitsFT232H.I2C.Backend` and
`CircuitsFT232H.I2C.Bus`. This module holds the shared MPSSE encoding
logic and bus-name conventions.

# `config`

```elixir
@type config() :: %{
  speed_hz: pos_integer(),
  clock_stretching: boolean(),
  flags: [Circuits.I2C.Bus.flag()]
}
```

Resolved I2C bus configuration.

# `build_config`

```elixir
@spec build_config(keyword()) :: {:ok, config()} | {:error, term()}
```

Validates and merges user options against the I2C defaults.

Options:
  * `:speed_hz` (default 100&nbsp;000, max 1&nbsp;000&nbsp;000)
  * `:clock_stretching` (default `false`) — enable adaptive-clocking-based
    I2C clock stretching detection. Requires `ADBUS0` (SCL) to be
    externally jumpered to `ADBUS7` so MPSSE can see when a slave is
    holding SCL low. Reserves `ADBUS7` (pin `AD7`) for the duration of
    the bus.

# `bus_name`

```elixir
@spec bus_name(CircuitsFT232H.Device.id()) :: String.t()
```

Builds the canonical I2C bus name for a device id.

# `close`

```elixir
@spec close(CircuitsFT232H.Device.id()) :: :ok
```

Tears down the I2C-specific MPSSE config so the chip can be reused for a
different mode without restarting the Device.

# `configure`

```elixir
@spec configure(CircuitsFT232H.Device.id(), config()) :: :ok | {:error, term()}
```

Configures the FT232H for I2C traffic with the given config. Assumes the
chip is in baseline MPSSE state and the I2C mode lock has been claimed.

Also runs an I2C bus-recovery sequence — 16 SCL pulses with SDA released
followed by a stop — to free any slave that's stuck mid-transaction (which
can happen if a previous program crashed during a read).

# `extra_reserved_pins`

```elixir
@spec extra_reserved_pins(config()) :: [CircuitsFT232H.Device.pin()]
```

Returns the additional pins this backend reserves for a given config —
used by `CircuitsFT232H.I2C.Backend` when claiming the I2C mode lock.

With clock stretching enabled, `ADBUS7` (`AD7`) is reserved as the
SCL-feedback input.

# `find_descriptor`

```elixir
@spec find_descriptor(String.t()) ::
  {:ok, CircuitsFT232H.USB.Descriptor.t()}
  | {:error, :invalid_bus_name | :not_found | term()}
```

Looks up the `USB.Descriptor` whose id matches the I2C bus name.

# `parse_bus_name`

```elixir
@spec parse_bus_name(String.t()) ::
  {:ok, CircuitsFT232H.Device.id()} | {:error, :invalid_bus_name}
```

Parses an I2C bus name into the underlying device id, or returns
`{:error, :invalid_bus_name}`.

# `read`

```elixir
@spec read(CircuitsFT232H.Device.id(), config(), 0..127, pos_integer(), keyword()) ::
  {:ok, binary()} | {:error, term()}
```

Reads `count` bytes from `address`.

# `write`

```elixir
@spec write(CircuitsFT232H.Device.id(), config(), 0..127, binary(), keyword()) ::
  :ok | {:error, term()}
```

Writes `data` to `address`. Empty `data` is the I2C device-present probe.

# `write_read`

```elixir
@spec write_read(
  CircuitsFT232H.Device.id(),
  config(),
  0..127,
  binary(),
  pos_integer(),
  keyword()
) ::
  {:ok, binary()} | {:error, term()}
```

Writes `write_data` then reads `read_count` bytes, with a repeated start in between.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
