blob: d68460dcdddd1b0fd7d86296d3ae0612fe420566 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "dw-i2c.h"
#include <lib/device-protocol/platform-device.h>
#include <lib/sync/completion.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/process.h>
#include <memory>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/mmio-buffer.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/i2cimpl.h>
#include <ddk/protocol/platform/bus.h>
#include <ddk/protocol/platform/device.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <hw/reg.h>
#include "src/devices/i2c/drivers/dw-i2c/dw_i2c-bind.h"
namespace dw_i2c {
zx_status_t DwI2cBus::Dumpstate() {
zxlogf(INFO, "DW_kI2cEnable_STATUS = \t0x%x",
EnableStatusReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_kI2cEnable = \t0x%x", EnableReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_CON = \t0x%x", ControlReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_TAR = \t0x%x", TargetAddressReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_HS_MADDR = \t0x%x", HSMasterAddrReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_SS_SCL_HCNT = \t0x%x",
StandardSpeedSclHighCountReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_SS_SCL_LCNT = \t0x%x",
StandardSpeedSclLowCountReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_FS_SCL_HCNT = \t0x%x",
FastSpeedSclHighCountReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_FS_SCL_LCNT = \t0x%x",
FastSpeedSclLowCountReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_INTR_MASK = \t0x%x", InterruptMaskReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_RAW_INTR_STAT = \t0x%x",
RawInterruptStatusReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_RX_TL = \t0x%x", RxFifoThresholdReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_TX_TL = \t0x%x", TxFifoThresholdReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_STATUS = \t0x%x", StatusReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_TXFLR = \t0x%x", TxFifoLevelReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_RXFLR = \t0x%x", RxFifoLevelReg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_COMP_PARAM_1 = \t0x%x", CompParam1Reg::Get().ReadFrom(&mmio_).reg_value());
zxlogf(INFO, "DW_I2C_TX_ABRT_SOURCE = \t0x%x",
TxAbrtSourceReg::Get().ReadFrom(&mmio_).reg_value());
return ZX_OK;
}
zx_status_t DwI2cBus::EnableAndWait(bool enable) {
uint32_t poll = 0;
// Set enable bit.
auto enable_reg = EnableReg::Get().ReadFrom(&mmio_);
enable_reg.set_enable(enable).WriteTo(&mmio_);
do {
if (EnableStatusReg::Get().ReadFrom(&mmio_).enable() == enable) {
// We are done. Exit.
return ZX_OK;
}
// Sleep 10 times the signaling period for the highest i2c transfer speed (400K) ~25uS.
zx_nanosleep(zx_deadline_after(kPollSleep));
} while (poll++ < kMaxPoll);
zxlogf(ERROR, "%s: Could not %s I2C contoller! DW_kI2cEnable_STATUS = 0x%x", __FUNCTION__,
enable ? "enable" : "disable", EnableStatusReg::Get().ReadFrom(&mmio_).enable());
Dumpstate();
return ZX_ERR_TIMED_OUT;
}
zx_status_t DwI2cBus::Enable() { return EnableAndWait(kI2cEnable); }
void DwI2cBus::ClearInterrupts() {
// Reading this register will clear all the interrupts.
ClearInterruptReg::Get().ReadFrom(&mmio_);
}
void DwI2cBus::DisableInterrupts() { InterruptMaskReg::Get().FromValue(0).WriteTo(&mmio_); }
void DwI2cBus::EnableInterrupts(uint32_t flag) {
InterruptMaskReg::Get().FromValue(flag).WriteTo(&mmio_);
}
zx_status_t DwI2cBus::Disable() { return EnableAndWait(kI2cDisable); }
zx_status_t DwI2cBus::WaitEvent(uint32_t sig_mask) {
uint32_t observed = 0;
auto deadline = zx::deadline_after(timeout_);
sig_mask |= kErrorSignal;
zx_status_t status = event_.wait_one(sig_mask, deadline, &observed);
if (status != ZX_OK) {
return status;
}
event_.signal(observed, 0);
if (observed & kErrorSignal) {
return ZX_ERR_TIMED_OUT;
}
return ZX_OK;
}
InterruptStatusReg DwI2cBus::ReadAndClearIrq() {
auto irq = InterruptStatusReg::Get().ReadFrom(&mmio_);
if (irq.tx_abrt()) {
// ABRT_SOURCE should be read before clearing TX_ABRT.
zxlogf(ERROR, "dw-i2c: error on bus - Abort source 0x%x",
TxAbrtSourceReg::Get().ReadFrom(&mmio_).reg_value());
ClearTxAbrtReg::Get().ReadFrom(&mmio_);
}
if (irq.start_det()) {
ClearStartDetReg::Get().ReadFrom(&mmio_);
}
if (irq.activity()) {
ClearActivityReg::Get().ReadFrom(&mmio_);
}
if (irq.stop_det()) {
ClearStopDetReg::Get().ReadFrom(&mmio_);
}
return irq;
}
// Thread to handle interrupts.
int DwI2cBus::IrqThread() {
zx_status_t status;
while (1) {
status = irq_.wait(nullptr);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: irq wait failed, retcode = %d", __FUNCTION__, status);
return status;
}
fbl::AutoLock lock(&ops_lock_);
if (ops_ == nullptr) {
continue;
}
auto reg = ReadAndClearIrq();
if (reg.tx_abrt()) {
if (event_.signal(0, kErrorSignal) != ZX_OK) {
zxlogf(ERROR, "Failure signaling I2C error - %d", status);
}
ops_ = nullptr;
}
if (reg.rx_full()) {
if (Receive() != ZX_OK) {
if (event_.signal(0, kErrorSignal) != ZX_OK) {
zxlogf(ERROR, "Failure signaling I2C error - %d", status);
}
ops_ = nullptr;
}
}
if (reg.tx_empty()) {
if (Transmit() != ZX_OK) {
if (event_.signal(0, kErrorSignal) != ZX_OK) {
zxlogf(ERROR, "Failure signaling I2C error - %d", status);
}
ops_ = nullptr;
}
}
if (reg.stop_det()) {
// Signal complete when all tx/rx are complete.
if (tx_op_idx_ == ops_count_ && rx_pending_ == 0) {
if (event_.signal(0, kTransactionCompleteSignal) != ZX_OK) {
zxlogf(ERROR, "Failure signaling I2C error - %d", status);
}
ops_ = nullptr;
}
}
} // while (1)
return ZX_OK;
}
zx_status_t DwI2cBus::WaitBusBusy() {
uint32_t timeout = 0;
auto status = StatusReg::Get();
while (status.ReadFrom(&mmio_).activity()) {
if (timeout > 100) {
return ZX_ERR_TIMED_OUT;
}
zx_nanosleep(zx_deadline_after(ZX_USEC(10)));
timeout++;
}
return ZX_OK;
}
void DwI2cBus::SetOpsHelper(const i2c_impl_op_t* ops, size_t count) {
fbl::AutoLock lock(&ops_lock_);
ops_ = ops;
ops_count_ = count;
rx_op_idx_ = 0;
tx_op_idx_ = 0;
rx_done_len_ = 0;
tx_done_len_ = 0;
send_restart_ = false;
rx_pending_ = 0;
}
zx_status_t DwI2cBus::Transact(const i2c_impl_op_t* rws, size_t count) {
fbl::AutoLock lock(&transact_lock_);
auto status = WaitBusBusy();
if (status != ZX_OK) {
zxlogf(ERROR, "I2C bus wait failed %d", status);
return status;
}
status = SetSlaveAddress(rws[0].address);
if (status != ZX_OK) {
zxlogf(ERROR, "I2C set address failed %d", status);
return status;
}
DisableInterrupts();
SetOpsHelper(rws, count);
status = Enable();
if (status != ZX_OK) {
zxlogf(ERROR, "I2C device enable failed %d", status);
return status;
}
event_.signal(kTransactionCompleteSignal | kErrorSignal, 0);
ClearInterrupts();
EnableInterrupts(kI2cInterruptDefaultMask);
status = WaitEvent(kTransactionCompleteSignal);
auto disable_ret = Disable();
if (disable_ret != ZX_OK) {
zxlogf(ERROR, "I2C device disable failed %d", disable_ret);
}
return status;
}
zx_status_t DwI2cBus::SetSlaveAddress(uint16_t addr) {
if (addr & (~k7BitAddrMask)) {
// support 7bit for now
return ZX_ERR_NOT_SUPPORTED;
}
addr &= k7BitAddrMask;
auto reg = TargetAddressReg::Get().ReadFrom(&mmio_);
reg.set_target_address(addr).set_master_10bitaddr(0);
reg.WriteTo(&mmio_);
return ZX_OK;
}
zx_status_t DwI2cBus::Receive() {
if (rx_pending_ == 0) {
zxlogf(ERROR, "dw-i2c: Bytes received without being requested");
return ZX_ERR_IO_OVERRUN;
}
uint32_t avail_read = RxFifoLevelReg::Get().ReadFrom(&mmio_).rx_fifo_level();
while ((avail_read != 0) && (rx_op_idx_ < ops_count_)) {
const i2c_impl_op_t* op = &ops_[rx_op_idx_];
if (!op->is_read) {
rx_op_idx_++;
continue;
}
uint8_t* buff = static_cast<uint8_t*>(op->data_buffer) + rx_done_len_;
*buff = static_cast<uint8_t>(DataCommandReg::Get().ReadFrom(&mmio_).data());
rx_done_len_++;
rx_pending_--;
if (rx_done_len_ == op->data_size) {
rx_op_idx_++;
rx_done_len_ = 0;
}
avail_read--;
}
if (avail_read != 0) {
zxlogf(ERROR, "dw-i2c: %d more bytes received than requested", avail_read);
return ZX_ERR_IO_OVERRUN;
}
return ZX_OK;
}
zx_status_t DwI2cBus::Transmit() {
uint32_t tx_limit;
tx_limit = tx_fifo_depth_ - TxFifoLevelReg::Get().ReadFrom(&mmio_).tx_fifo_level();
// TODO(fxbug.dev/34403)
// If IC_EMPTYFIFO_HOLD_MASTER_EN = 0, then STOP is sent on TX_EMPTY. All commands should be
// queued up as soon as possible to avoid this. Possible race leading to failed
// transaction, if the irq thread is deschedule in the midst for tx command queuing.
// This is the mode used in as370 and currently this issue is not addressed.
// See bug fxbug.dev/34403 for details.
// If IC_EMPTYFIFO_HOLD_MASTER_EN = 1, then STOP and RESTART must be sent explicitly, which is
// handled by this code.
while ((tx_limit != 0) && (tx_op_idx_ < ops_count_)) {
const i2c_impl_op_t* op = &ops_[tx_op_idx_];
uint8_t* buff = static_cast<uint8_t*>(op->data_buffer) + tx_done_len_;
size_t len = op->data_size - tx_done_len_;
ZX_DEBUG_ASSERT(len <= kMaxTransfer);
auto cmd = DataCommandReg::Get().FromValue(0);
// Send STOP cmd if last byte and stop set.
if (len == 1 && op->stop) {
cmd.set_stop(1);
}
// Send restart.
if (send_restart_) {
cmd.set_start(1);
send_restart_ = false;
}
if (op->is_read) {
// Read command should be queued for each byte.
cmd.set_command(1);
rx_pending_++;
// Set receive threshold to 1 less than the expected size.
// Do this only once.
if (tx_done_len_ == 0) {
RxFifoThresholdReg::Get()
.FromValue(0)
.set_rx_threshold_level(static_cast<uint32_t>(op->data_size - 1))
.WriteTo(&mmio_);
}
} else {
cmd.set_data(*buff++);
}
cmd.WriteTo(&mmio_);
tx_done_len_++;
if (tx_done_len_ == op->data_size) {
tx_op_idx_++;
tx_done_len_ = 0;
send_restart_ = true;
}
tx_limit--;
}
if (tx_op_idx_ == ops_count_) {
// All tx are complete. Remove TX_EMPTY from interrupt mask.
EnableInterrupts(kI2cInterruptReadMask);
}
return ZX_OK;
}
void DwI2cBus::ShutDown() {
irq_.destroy();
if (irq_thread_.joinable()) {
irq_thread_.join();
}
}
zx_status_t DwI2cBus::HostInit() {
// Make sure we are truly running on a DesignWire IP.
auto dw_comp_type = CompTypeReg::Get().ReadFrom(&mmio_).reg_value();
if (dw_comp_type != kDwCompTypeNum) {
zxlogf(ERROR, "%s: Incompatible IP Block detected. Expected = 0x%x, Actual = 0x%x",
__FUNCTION__, kDwCompTypeNum, dw_comp_type);
return ZX_ERR_NOT_SUPPORTED;
}
// Read the various capabilities of the fragment.
auto comp_reg = CompParam1Reg::Get().ReadFrom(&mmio_);
tx_fifo_depth_ = comp_reg.tx_buffer_depth();
rx_fifo_depth_ = comp_reg.rx_buffer_depth();
// Minimum fifo depth would be max transfer limit.
kMaxTransfer = tx_fifo_depth_ > rx_fifo_depth_ ? rx_fifo_depth_ : tx_fifo_depth_;
/* I2C Block Initialization based on DW_apb_i2c_databook Section 7.3 */
// Disable I2C Block.
Disable();
// Configure the controller:
auto ctrl_reg = ControlReg::Get().FromValue(0);
// - slave disable
ctrl_reg.set_slave_disable(1);
// - enable restart mode
ctrl_reg.set_restart_en(1);
// - set 7-bit address modeset
ctrl_reg.set_master_10bitaddr(k7BitAddr);
ctrl_reg.set_slave_10bitaddr(k7BitAddr);
// - set speed to fast, master enable
ctrl_reg.set_max_speed_mode(kFastMode);
// - set master enable
ctrl_reg.set_master_mode(1);
// Write final mask.
ctrl_reg.WriteTo(&mmio_);
// Write SS/FS LCNT and HCNT.
StandardSpeedSclHighCountReg::Get()
.ReadFrom(&mmio_)
.set_ss_scl_hcnt(kSclStandardSpeedHcnt)
.WriteTo(&mmio_);
StandardSpeedSclLowCountReg::Get()
.ReadFrom(&mmio_)
.set_ss_scl_lcnt(kSclStandardSpeedLcnt)
.WriteTo(&mmio_);
FastSpeedSclHighCountReg::Get()
.ReadFrom(&mmio_)
.set_fs_scl_hcnt(kSclFastSpeedHcnt)
.WriteTo(&mmio_);
FastSpeedSclLowCountReg::Get()
.ReadFrom(&mmio_)
.set_fs_scl_lcnt(kSclFastSpeedLcnt)
.WriteTo(&mmio_);
// Set SDA Hold time.
// Enable SDA hold for RX as well.
SdaHoldReg::Get()
.FromValue(0)
.set_sda_hold_time_tx(kSdaHoldValue)
.set_sda_hold_time_rx(kSdaHoldValue)
.WriteTo(&mmio_);
// Setup TX and RX FIFO Thresholds.
TxFifoThresholdReg::Get()
.ReadFrom(&mmio_)
.set_tx_threshold_level(tx_fifo_depth_ / 2)
.WriteTo(&mmio_);
RxFifoThresholdReg::Get().ReadFrom(&mmio_).set_rx_threshold_level(0).WriteTo(&mmio_);
// Disable interrupts.
DisableInterrupts();
return ZX_OK;
}
zx_status_t DwI2cBus::Init() {
zx_status_t status;
timeout_ = zx::duration(kDefaultTimeout);
status = zx::event::create(0, &event_);
if (status != ZX_OK) {
return status;
}
// Initialize i2c host controller.
status = HostInit();
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to initialize i2c host controller %d", __FUNCTION__, status);
return status;
}
irq_thread_ = std::thread(&DwI2cBus::IrqThread, this);
return ZX_OK;
}
#define I2C_AS370_DW_TEST 0
#if I2C_AS370_DW_TEST
int DwI2c::TestThread() {
zx_status_t status;
bool pass = true;
uint8_t addr = 0; // PMIC device
uint8_t valid_addr = 0x66; // SY20212DAIC PMIC device
uint8_t valid_value = 0x8B; // Register 0x0 default value for PMIC
uint8_t data_write = 0;
uint8_t data_read;
#if 0
zxlogf(INFO, "Finding I2c devices");
// Find all available devices.
for (uint32_t i = 0x0; i <= 0x7f; i++) {
addr = static_cast<uint8_t>(i);
i2c_impl_op_t ops[] = {
{.address = addr,
.data_buffer = &data_write,
.data_size = 1,
.is_read = false,
.stop = false},
{.address = addr, .data_buffer = &data_read, .data_size = 1, .is_read = true, .stop = true},
};
status = I2cImplTransact(0, ops, countof(ops));
if (status == ZX_OK) {
zxlogf(INFO, "I2C device found at address: 0x%02X", addr);
}
}
#endif
zxlogf(INFO, "I2C: Testing PMIC ping");
// Test multiple reads from a known device.
for (uint32_t i = 0; i < 10; i++) {
addr = valid_addr;
data_read = 0;
i2c_impl_op_t ops[] = {
{.address = addr,
.data_buffer = &data_write,
.data_size = 1,
.is_read = false,
.stop = false},
{.address = addr, .data_buffer = &data_read, .data_size = 1, .is_read = true, .stop = true},
};
status = I2cImplTransact(0, ops, countof(ops));
if (status == ZX_OK) {
// Check with reset value of PMIC registers.
if (data_read != valid_value) {
zxlogf(INFO, "I2C test: PMIC register value does not matched - %x", data_read);
pass = false;
}
} else {
zxlogf(INFO, "I2C test: PMIC ping failed : %d", status);
pass = false;
}
}
if (pass) {
zxlogf(INFO, "DW I2C test for AS370 passed");
} else {
zxlogf(ERROR, "DW I2C test for AS370 failed");
}
return 0;
}
#endif
zx_status_t DwI2c::I2cImplTransact(uint32_t bus_id, const i2c_impl_op_t* rws, size_t count) {
if (bus_id >= bus_count_) {
return ZX_ERR_INVALID_ARGS;
}
for (uint32_t i = 0; i < count; ++i) {
if (rws[i].data_size > buses_[bus_id]->kMaxTransfer) {
return ZX_ERR_OUT_OF_RANGE;
}
}
if (count == 0) {
return ZX_OK;
}
for (uint32_t i = 1; i < count; ++i) {
if (rws[i].address != rws[0].address) {
return ZX_ERR_NOT_SUPPORTED;
}
}
return buses_[bus_id]->Transact(rws, count);
}
zx_status_t DwI2c::I2cImplSetBitrate(uint32_t bus_id, uint32_t bitrate) {
// TODO: currently supports FAST_MODE - 400kHz
return ZX_ERR_NOT_SUPPORTED;
}
uint32_t DwI2c::I2cImplGetBusCount() { return static_cast<uint32_t>(bus_count_); }
zx_status_t DwI2c::I2cImplGetMaxTransferSize(uint32_t bus_id, size_t* out_size) {
if (bus_id >= bus_count_) {
return ZX_ERR_INVALID_ARGS;
}
*out_size = buses_[bus_id]->kMaxTransfer;
return ZX_OK;
}
void DwI2c::ShutDown() {
for (uint32_t id = 0; id < bus_count_; id++) {
buses_[id]->ShutDown();
}
}
void DwI2c::DdkUnbind(ddk::UnbindTxn txn) {
ShutDown();
txn.Reply();
}
void DwI2c::DdkRelease() { delete this; }
zx_status_t DwI2c::Init() {
auto cleanup = fbl::MakeAutoCall([&]() { ShutDown(); });
#if I2C_AS370_DW_TEST
thrd_t test_thread;
auto thunk = [](void* arg) -> int { return reinterpret_cast<DwI2c*>(arg)->TestThread(); };
int rc = thrd_create_with_name(&test_thread, thunk, this, "dw-i2c-test");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
#endif
cleanup.cancel();
return ZX_OK;
}
zx_status_t DwI2c::Create(void* ctx, zx_device_t* parent) {
zx_status_t status;
fbl::AllocChecker ac;
ddk::PDev pdev(parent);
if (!pdev.is_valid()) {
zxlogf(ERROR, "%s: Failed to get ZX_PROTOCOL_PDEV", __FILE__);
return ZX_ERR_NO_RESOURCES;
}
pdev_device_info_t info;
if ((status = pdev.GetDeviceInfo(&info)) != ZX_OK) {
zxlogf(ERROR, "dw_i2c: pdev_get_device_info failed");
return ZX_ERR_NOT_SUPPORTED;
}
if (info.mmio_count != info.irq_count) {
zxlogf(ERROR, "dw_i2c: mmio_count %u does not matchirq_count %u", info.mmio_count,
info.irq_count);
return ZX_ERR_INVALID_ARGS;
}
fbl::Vector<std::unique_ptr<DwI2cBus>> bus_list;
for (uint32_t i = 0; i < info.mmio_count; i++) {
std::optional<ddk::MmioBuffer> mmio;
if ((status = pdev.MapMmio(i, &mmio)) != ZX_OK) {
zxlogf(ERROR, "%s: pdev_map_mmio_buffer failed %d", __FUNCTION__, status);
return status;
}
zx::interrupt irq;
if ((status = pdev.GetInterrupt(i, &irq)) != ZX_OK) {
return status;
}
auto i2c_bus = fbl::make_unique_checked<DwI2cBus>(&ac, *std::move(mmio), irq);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if ((status = i2c_bus->Init()) != ZX_OK) {
zxlogf(ERROR, "dw_i2c: dw_i2c bus init failed: %d", status);
return ZX_ERR_INTERNAL;
}
bus_list.push_back(std::move(i2c_bus), &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
}
auto dev = fbl::make_unique_checked<DwI2c>(&ac, parent, std::move(bus_list));
if (!ac.check()) {
zxlogf(ERROR, "%s ZX_ERR_NO_MEMORY", __FUNCTION__);
return ZX_ERR_NO_MEMORY;
}
if ((status = dev->DdkAdd("dw-i2c")) != ZX_OK) {
zxlogf(ERROR, "%s DdkAdd failed: %d", __FUNCTION__, status);
dev->ShutDown();
return status;
}
// Devmgr is now in charge of the memory for dev.
auto ptr = dev.release();
return ptr->Init();
}
static zx_driver_ops_t dw_i2c_driver_ops = {
.version = DRIVER_OPS_VERSION,
.init = nullptr,
.bind = DwI2c::Create,
.create = nullptr,
.release = nullptr,
.run_unit_tests = nullptr,
};
} // namespace dw_i2c
// clang-format off
ZIRCON_DRIVER(dw_i2c, dw_i2c::dw_i2c_driver_ops, "zircon", "0.1");
//clang-format on