blob: 96e65c65cb8d32d28055eb863770c825bb131157 [file] [log] [blame]
// Copyright 2016 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 "intel-i2c-controller.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <fuchsia/hardware/i2c/c/fidl.h>
#include <lib/device-protocol/pci.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/hw/i2c.h>
#include <zircon/listnode.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <memory>
#include <vector>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/metadata/i2c.h>
#include <ddk/protocol/i2c.h>
#include <ddk/protocol/i2cimpl.h>
#include <ddk/protocol/pci.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <hw/pci.h>
#include "binding.h"
namespace intel_i2c {
inline void RmwReg32(MMIO_PTR volatile uint32_t* addr, uint32_t startbit, uint32_t width,
uint32_t val) {
return MmioWrite32((MmioRead32(addr) & ~(((1 << width) - 1) << startbit)) | (val << startbit),
addr);
}
constexpr uint32_t kDevidleControl = 0x24c;
constexpr uint32_t kDevidleControlCmdInProgress = 0;
constexpr uint32_t kDevidleControlDevidle = 2;
constexpr uint32_t kDevidleControlRestoreRequired = 3;
// Number of entries at which the FIFO level triggers happen
constexpr uint32_t kDefaultRxFifoTriggerLevel = 8;
constexpr uint32_t kDefaultTxFifoTriggerLevel = 8;
// Signals used on the controller's event_handle
constexpr uint32_t kRxFullSignal = ZX_USER_SIGNAL_0;
constexpr uint32_t kTxEmptySignal = ZX_USER_SIGNAL_1;
constexpr uint32_t kStopDetectedSignal = ZX_USER_SIGNAL_2;
constexpr uint32_t kErrorDetectedSignal = ZX_USER_SIGNAL_3;
// More than enough
constexpr size_t MAX_TRANSFER_SIZE = (UINT16_MAX - 1);
constexpr uint32_t kIntelDesignwareCompType = 0x44570140;
zx_status_t IntelI2cController::Create(void* ctx, zx_device_t* parent) {
fbl::AllocChecker ac;
auto dev = std::unique_ptr<IntelI2cController>(new (&ac) IntelI2cController(parent));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto status = dev->Init();
if (status != ZX_OK) {
return status;
}
__UNUSED auto* dummy = dev.release();
return ZX_OK;
}
zx_status_t IntelI2cController::Init() {
mtx_init(&mutex_, mtx_plain);
mtx_init(&irq_mask_mutex_, mtx_plain);
uint16_t vendor_id;
uint16_t device_id;
pci_.ConfigRead16(PCI_CONFIG_VENDOR_ID, &vendor_id);
pci_.ConfigRead16(PCI_CONFIG_DEVICE_ID, &device_id);
auto status = pci_.MapMmio(0u, ZX_CACHE_POLICY_UNCACHED_DEVICE, &mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c: failed to map mmio 0: %d", status);
return status;
}
regs_ = reinterpret_cast<MMIO_PTR I2cRegs*>(mmio_->get());
status = pci_.ConfigureIrqMode(1);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c: failed to set irq mode: %d", status);
return status;
}
// get irq handle
status = pci_.MapInterrupt(0, &irq_handle_);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c: failed to get irq handle: %d", status);
return status;
}
status = zx::event::create(0, &event_handle_);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c: failed to create event handle: %d", status);
return status;
}
// start irq thread
int ret = thrd_create_with_name(
&irq_thread_,
[](void* arg) -> int { return reinterpret_cast<IntelI2cController*>(arg)->IrqThread(); },
this, "i2c-irq");
if (ret != thrd_success) {
zxlogf(ERROR, "i2c: failed to create irq thread: %d", ret);
return status;
}
// Run the bus at standard speed by default.
bus_freq_ = kI2cMaxStandardSpeedHz;
status = DeviceSpecificInit(device_id);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c: device specific init failed: %d", status);
return status;
}
status = ComputeBusTiming();
if (status < 0) {
zxlogf(ERROR, "i2c: compute bus timing failed: %d", status);
return status;
}
// Temporary hack until we have routed through the FMCN ACPI tables.
if (vendor_id == INTEL_VID && device_id == INTEL_SUNRISE_POINT_SERIALIO_I2C0_DID) {
// TODO: These should all be extracted from FPCN in the ACPI tables.
fmp_scl_lcnt_ = 0x0042;
fmp_scl_hcnt_ = 0x001b;
sda_hold_ = 0x24;
} else if (vendor_id == INTEL_VID && device_id == INTEL_SUNRISE_POINT_SERIALIO_I2C1_DID) {
// TODO(yky): These should all be extracted from FMCN in the ACPI tables.
fs_scl_lcnt_ = 0x00b6;
fs_scl_hcnt_ = 0x0059;
sda_hold_ = 0x24;
} else if (vendor_id == INTEL_VID && device_id == INTEL_SUNRISE_POINT_SERIALIO_I2C2_DID) {
// TODO: These should all be extracted from FMCN in the ACPI tables.
fs_scl_lcnt_ = 0x00ba;
fs_scl_hcnt_ = 0x005d;
sda_hold_ = 0x24;
} else if (vendor_id == INTEL_VID && device_id == INTEL_SUNRISE_POINT_SERIALIO_I2C4_DID) {
// TODO: These should all be extracted from FMCN in the ACPI tables.
fs_scl_lcnt_ = 0x005a;
fs_scl_hcnt_ = 0x00a6;
sda_hold_ = 0x24;
}
// Configure the I2C controller.
fbl::AutoLock lock(&mutex_);
status = Reset();
if (status < 0) {
zxlogf(ERROR, "i2c: reset controller failed: %d", status);
return status;
}
// We add one device. This device holds DEVICE_METADATA_I2C_CHANNELS
// which contains info for each child device.
// TODO: This should be a composite device that also holds interrupt information.
char name[ZX_DEVICE_NAME_MAX];
snprintf(name, sizeof(name), "i2c-bus-%04x", device_id);
status = DdkAdd(name);
if (status < 0) {
zxlogf(ERROR, "device add failed: %s", zx_status_get_string(status));
return status;
}
zxlogf(INFO,
"initialized intel serialio i2c driver, "
"reg=%p regsize=%ld",
regs_, mmio_->get_size());
return ZX_OK;
}
void IntelI2cController::DdkInit(ddk::InitTxn txn) {
auto status = AddSubordinates();
if (status != ZX_OK) {
zxlogf(ERROR, "adding subordinates failed: %s", zx_status_get_string(status));
txn.Reply(status);
return;
}
fbl::AutoLock lock(&mutex_);
std::vector<i2c_channel_t> i2c_channels(subordinates_.size());
size_t i = 0;
for (auto const& it : subordinates_) {
i2c_channel_t& chan = i2c_channels[i++];
auto& subordinate = it.second;
chan.bus_id = 0;
chan.vid = subordinate->vendor_id();
chan.pid = 0;
chan.did = subordinate->device_id();
chan.address = subordinate->GetChipAddress();
chan.i2c_class = subordinate->GetI2cClass();
}
status = DdkAddMetadata(DEVICE_METADATA_I2C_CHANNELS, i2c_channels.data(),
i2c_channels.size() * sizeof(i2c_channel_t));
if (status != ZX_OK) {
zxlogf(ERROR, "adding device metadata failed: %s\n", zx_status_get_string(status));
txn.Reply(status);
return;
}
txn.Reply(ZX_OK);
}
zx_status_t IntelI2cController::I2cImplTransact(const uint32_t bus_id, const i2c_impl_op_t* op_list,
const size_t op_count) {
if (op_count == 0) {
return ZX_OK;
}
fbl::AutoLock lock(&mutex_);
// Every op has the same address/subordinate.
auto it = subordinates_.find(op_list->address);
if (it == subordinates_.end()) {
return ZX_ERR_NOT_FOUND;
}
auto& subordinate = it->second;
IntelI2cSubordinateSegment segs[I2C_MAX_RW_OPS];
if (op_count >= I2C_MAX_RW_OPS) {
return ZX_ERR_NOT_SUPPORTED;
}
for (size_t i = 0; i < op_count; ++i) {
segs[i].buf = reinterpret_cast<uint8_t*>(op_list[i].data_buffer);
segs[i].len = static_cast<int>(op_list[i].data_size);
if (op_list[i].is_read) {
segs[i].type = fuchsia_hardware_i2c_SegmentType_READ;
} else {
segs[i].type = fuchsia_hardware_i2c_SegmentType_WRITE;
}
}
zx_status_t status = subordinate->Transfer(segs, static_cast<int>(op_count));
if (status != ZX_OK) {
zxlogf(ERROR, "intel-i2c-controller: subordinate transfer failed with: %d\n", status);
Reset();
}
return status;
}
uint32_t IntelI2cController::I2cImplGetBusCount() { return 1; }
zx_status_t IntelI2cController::I2cImplGetMaxTransferSize(const uint32_t bus_id, size_t* out_size) {
*out_size = MAX_TRANSFER_SIZE;
return ZX_OK;
}
zx_status_t IntelI2cController::I2cImplSetBitrate(const uint32_t bus_id, const uint32_t bitrate) {
// TODO: implement
return ZX_ERR_NOT_SUPPORTED;
}
uint8_t IntelI2cController::ExtractTxFifoDepthFromParam(const uint32_t param) {
return ((param >> 16) & 0xff) + 1;
}
uint8_t IntelI2cController::ExtractRxFifoDepthFromParam(const uint32_t param) {
return ((param >> 8) & 0xff) + 1;
}
uint32_t IntelI2cController::ChipAddrMask(const int width) { return ((1 << width) - 1); }
zx_status_t IntelI2cController::AddSubordinate(const uint8_t width, const uint16_t address,
const zx_device_prop_t* props,
const uint32_t propcount) {
if ((width != kI2c7BitAddress && width != kI2c10BitAddress) ||
(address & ~ChipAddrMask(width)) != 0) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&mutex_);
// Make sure a subordinate with the given address doesn't already exist.
auto it = subordinates_.find(address);
if (it != subordinates_.end()) {
return ZX_ERR_ALREADY_EXISTS;
}
uint32_t i2c_class = 0;
uint16_t vendor_id = 0;
uint16_t device_id = 0;
for (uint32_t i = 0; i < propcount; i++) {
if (props[i].id == BIND_I2C_CLASS) {
i2c_class = props[i].value;
} else if (props[i].id == BIND_I2C_VID) {
vendor_id = static_cast<uint16_t>(props[i].value);
} else if (props[i].id == BIND_I2C_DID) {
device_id = static_cast<uint16_t>(props[i].value);
}
}
auto subordinate =
IntelI2cSubordinate::Create(this, width, address, i2c_class, vendor_id, device_id);
if (subordinate == nullptr) {
zxlogf(ERROR, "Failed to create subordinate.");
return ZX_ERR_INVALID_ARGS;
}
subordinates_[address] = std::move(subordinate);
return ZX_OK;
}
uint32_t IntelI2cController::ComputeSclHcnt(const uint32_t controller_freq,
const uint32_t t_high_nanos, const uint32_t t_r_nanos) {
uint32_t clock_freq_kilohz = controller_freq / 1000;
// We need high count to satisfy highcount + 3 >= clock * (t_HIGH + t_r_max)
// Apparently the counter starts as soon as the controller releases SCL, so
// include t_r to account for potential delay in rising.
//
// In terms of units, the division should really be thought of as a
// (1 s)/(1000000000 ns) factor to get this into the right scale.
uint32_t high_count = (clock_freq_kilohz * (t_high_nanos + t_r_nanos) + 500000);
return high_count / 1000000 - 3;
}
uint32_t IntelI2cController::ComputeSclLcnt(const uint32_t controller_freq,
const uint32_t t_low_nanos, const uint32_t t_f_nanos) {
uint32_t clock_freq_kilohz = controller_freq / 1000;
// We need low count to satisfy lowcount + 1 >= clock * (t_LOW + t_f_max)
// Apparently the counter starts as soon as the controller pulls SCL low, so
// include t_f to account for potential delay in falling.
//
// In terms of units, the division should really be thought of as a
// (1 s)/(1000000000 ns) factor to get this into the right scale.
uint32_t low_count = (clock_freq_kilohz * (t_low_nanos + t_f_nanos) + 500000);
return low_count / 1000000 - 1;
}
zx_status_t IntelI2cController::ComputeBusTiming() {
// These constants are from the i2c timing requirements
uint32_t fmp_hcnt = ComputeSclHcnt(controller_freq_, 260, 120);
uint32_t fmp_lcnt = ComputeSclLcnt(controller_freq_, 500, 120);
uint32_t fs_hcnt = ComputeSclHcnt(controller_freq_, 600, 300);
uint32_t fs_lcnt = ComputeSclLcnt(controller_freq_, 1300, 300);
uint32_t ss_hcnt = ComputeSclHcnt(controller_freq_, 4000, 300);
uint32_t ss_lcnt = ComputeSclLcnt(controller_freq_, 4700, 300);
// Make sure the counts are within bounds.
if (fmp_hcnt >= (1 << 16) || fmp_hcnt < 6 || fmp_lcnt >= (1 << 16) || fmp_lcnt < 8) {
return ZX_ERR_OUT_OF_RANGE;
}
if (fs_hcnt >= (1 << 16) || fs_hcnt < 6 || fs_lcnt >= (1 << 16) || fs_lcnt < 8) {
return ZX_ERR_OUT_OF_RANGE;
}
if (ss_hcnt >= (1 << 16) || ss_hcnt < 6 || ss_lcnt >= (1 << 16) || ss_lcnt < 8) {
return ZX_ERR_OUT_OF_RANGE;
}
fmp_scl_hcnt_ = static_cast<uint16_t>(fmp_hcnt);
fmp_scl_lcnt_ = static_cast<uint16_t>(fmp_lcnt);
fs_scl_hcnt_ = static_cast<uint16_t>(fs_hcnt);
fs_scl_lcnt_ = static_cast<uint16_t>(fs_lcnt);
ss_scl_hcnt_ = static_cast<uint16_t>(ss_hcnt);
ss_scl_lcnt_ = static_cast<uint16_t>(ss_lcnt);
sda_hold_ = 1;
return ZX_OK;
}
zx_status_t IntelI2cController::SetBusFrequency(const uint32_t frequency) {
if (frequency != kI2cMaxFastSpeedHz && frequency != kI2cMaxStandardSpeedHz &&
frequency != kI2cMaxFastPlusSpeedHz) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&mutex_);
bus_freq_ = frequency;
zx_status_t status = Reset();
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
int IntelI2cController::IrqThread() {
zx_status_t status;
for (;;) {
status = irq_handle_.wait(nullptr);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c: error waiting for interrupt: %d", status);
break;
}
uint32_t intr_stat = MmioRead32(&regs_->intr_stat);
zxlogf(TRACE, "Received i2c interrupt: %x %x", intr_stat, MmioRead32(&regs_->raw_intr_stat));
if (intr_stat & (1u << kIntrRxUnder)) {
// If we hit an underflow, it's a bug.
event_handle_.signal(0, kErrorDetectedSignal);
MmioRead32(&regs_->clr_rx_under);
zxlogf(ERROR, "i2c: rx underflow detected!");
}
if (intr_stat & (1u << kIntrRxOver)) {
// If we hit an overflow, it's a bug.
event_handle_.signal(0, kErrorDetectedSignal);
MmioRead32(&regs_->clr_rx_over);
zxlogf(ERROR, "i2c: rx overflow detected!");
}
if (intr_stat & (1u << kIntrRxFull)) {
fbl::AutoLock lock(&irq_mask_mutex_);
event_handle_.signal(0, kRxFullSignal);
RmwReg32(&regs_->intr_mask, kIntrRxFull, 1, 0);
}
if (intr_stat & (1u << kIntrTxOver)) {
// If we hit an overflow, it's a bug.
event_handle_.signal(0, kErrorDetectedSignal);
MmioRead32(&regs_->clr_tx_over);
zxlogf(ERROR, "i2c: tx overflow detected!");
}
if (intr_stat & (1u << kIntrTxEmpty)) {
fbl::AutoLock lock(&irq_mask_mutex_);
event_handle_.signal(0, kTxEmptySignal);
RmwReg32(&regs_->intr_mask, kIntrTxEmpty, 1, 0);
}
if (intr_stat & (1u << kIntrTxAbort)) {
zxlogf(ERROR, "i2c: tx abort detected: 0x%08x", MmioRead32(&regs_->tx_abrt_source));
event_handle_.signal(0, kErrorDetectedSignal);
MmioRead32(&regs_->clr_tx_abort);
}
if (intr_stat & (1u << kIntrActivity)) {
// Should always be masked...remask it.
fbl::AutoLock lock(&irq_mask_mutex_);
RmwReg32(&regs_->intr_mask, kIntrActivity, 1, 0);
zxlogf(INFO, "i2c: spurious activity irq");
}
if (intr_stat & (1u << kIntrStopDetection)) {
event_handle_.signal(0, kStopDetectedSignal);
MmioRead32(&regs_->clr_stop_det);
}
if (intr_stat & (1u << kIntrStartDetection)) {
MmioRead32(&regs_->clr_start_det);
}
if (intr_stat & (1u << kIntrGeneralCall)) {
// Should always be masked...remask it.
fbl::AutoLock lock(&irq_mask_mutex_);
RmwReg32(&regs_->intr_mask, kIntrGeneralCall, 1, 0);
zxlogf(INFO, "i2c: spurious general call irq");
}
}
return 0;
}
zx_status_t IntelI2cController::WaitForRxFull(const zx::time deadline) {
uint32_t observed;
zx_status_t status =
event_handle_.wait_one(kRxFullSignal | kErrorDetectedSignal, deadline, &observed);
if (status != ZX_OK) {
return status;
}
if (observed & kErrorDetectedSignal) {
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t IntelI2cController::WaitForTxEmpty(const zx::time deadline) {
uint32_t observed;
zx_status_t status =
event_handle_.wait_one(kTxEmptySignal | kErrorDetectedSignal, deadline, &observed);
if (status != ZX_OK) {
return status;
}
if (observed & kErrorDetectedSignal) {
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t IntelI2cController::WaitForStopDetect(const zx::time deadline) {
uint32_t observed;
zx_status_t status =
event_handle_.wait_one(kStopDetectedSignal | kErrorDetectedSignal, deadline, &observed);
if (status != ZX_OK) {
return status;
}
if (observed & kErrorDetectedSignal) {
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t IntelI2cController::CheckForError() {
uint32_t observed;
zx_status_t status = event_handle_.wait_one(kErrorDetectedSignal, zx::time(0), &observed);
if (status != ZX_OK && status != ZX_ERR_TIMED_OUT) {
return status;
}
if (observed & kErrorDetectedSignal) {
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t IntelI2cController::ClearStopDetect() {
return event_handle_.signal(kStopDetectedSignal, 0);
}
// Perform a write to the DATA_CMD register, and clear
// interrupt masks as appropriate
zx_status_t IntelI2cController::IssueRx(const uint32_t data_cmd) {
MmioWrite32(data_cmd, &regs_->data_cmd);
return ZX_OK;
}
zx_status_t IntelI2cController::FlushRxFullIrq() {
fbl::AutoLock lock(&irq_mask_mutex_);
zx_status_t status = event_handle_.signal(kRxFullSignal, 0);
RmwReg32(&regs_->intr_mask, kIntrRxFull, 1, 1);
return status;
}
uint8_t IntelI2cController::ReadRx() { return static_cast<uint8_t>(MmioRead32(&regs_->data_cmd)); }
zx_status_t IntelI2cController::IssueTx(const uint32_t data_cmd) {
MmioWrite32(data_cmd, &regs_->data_cmd);
uint32_t tx_tl = GetTxFifoThreshold();
const uint32_t txflr = MmioRead32(&regs_->txflr) & 0x1ff;
// If we've raised the TX queue level above the threshold, clear the signal
// and unmask the interrupt.
if (txflr > tx_tl) {
fbl::AutoLock lock(&irq_mask_mutex_);
zx_status_t status = event_handle_.signal(kTxEmptySignal, 0);
RmwReg32(&regs_->intr_mask, kIntrTxEmpty, 1, 1);
return status;
}
return ZX_OK;
}
void IntelI2cController::Enable() { RmwReg32(&regs_->i2c_en, kI2cEnEnable, 1, 1); }
uint32_t IntelI2cController::GetRxFifoThreshold() { return (MmioRead32(&regs_->rx_tl) & 0xff) + 1; }
// Get an RX interrupt whenever the RX FIFO size is >= the threshold.
zx_status_t IntelI2cController::SetRxFifoThreshold(const uint32_t threshold) {
if (threshold - 1 > UINT8_MAX) {
return ZX_ERR_INVALID_ARGS;
}
RmwReg32(&regs_->rx_tl, 0, 8, threshold - 1);
return ZX_OK;
}
uint32_t IntelI2cController::GetRxFifoLevel() { return MmioRead32(&regs_->rxflr) & 0x1ff; }
bool IntelI2cController::IsRxFifoEmpty() {
return !(MmioRead32(&regs_->i2c_sta) & (0x1 << kI2cStaRfne));
}
bool IntelI2cController::IsTxFifoFull() {
return !(MmioRead32(&regs_->i2c_sta) & (0x1 << kI2cStaTfnf));
}
uint32_t IntelI2cController::GetTxFifoThreshold() { return (MmioRead32(&regs_->tx_tl) & 0xff) + 1; }
// Get a TX interrupt whenever the TX FIFO size is <= the threshold.
zx_status_t IntelI2cController::SetTxFifoThreshold(const uint32_t threshold) {
if (threshold - 1 > UINT8_MAX) {
return ZX_ERR_INVALID_ARGS;
}
RmwReg32(&regs_->tx_tl, 0, 8, threshold - 1);
return ZX_OK;
}
bool IntelI2cController::IsBusIdle() {
uint32_t i2c_sta = MmioRead32(&regs_->i2c_sta);
return !(i2c_sta & (0x1 << kI2cStaCa)) && (i2c_sta & (0x1 << kI2cStaTfce));
}
uint32_t IntelI2cController::StopDetected() {
return (MmioRead32(&regs_->raw_intr_stat) & (0x1 << kIntrStopDetection));
}
void IntelI2cController::SetAddressingMode(const uint32_t addr_mode_bit) {
RmwReg32(&regs_->ctl, kCtlAddressingMode, 1, addr_mode_bit);
}
void IntelI2cController::SetTargetAddress(const uint32_t addr_mode_bit, const uint32_t address) {
MmioWrite32((addr_mode_bit << kTarAddWidth) | (address << kTarAddIcTar), &regs_->tar_add);
}
zx_status_t IntelI2cController::Reset() {
zx_status_t status = ZX_OK;
// The register will only return valid values if the ACPI _PS0 has been
// evaluated.
if (MmioRead32(reinterpret_cast<MMIO_PTR uint32_t*>(reinterpret_cast<MMIO_PTR char*>(regs_) +
kDevidleControl)) != 0xffffffff) {
// Wake up device if it is in DevIdle state
RmwReg32(reinterpret_cast<MMIO_PTR uint32_t*>(reinterpret_cast<MMIO_PTR char*>(regs_) +
kDevidleControl),
kDevidleControlDevidle, 1, 0);
// Wait for wakeup to finish processing
int retry = 10;
while (retry-- && (MmioRead32(reinterpret_cast<MMIO_PTR uint32_t*>(
reinterpret_cast<MMIO_PTR char*>(regs_) + kDevidleControl)) &
(1 << kDevidleControlCmdInProgress))) {
usleep(10);
}
if (!retry) {
zxlogf(ERROR, "i2c-controller: timed out waiting for device idle");
return ZX_ERR_TIMED_OUT;
}
}
// Reset the device.
RmwReg32(soft_reset_, 0, 2, 0x0);
RmwReg32(soft_reset_, 0, 2, 0x3);
// Clear the "Restore Required" flag
RmwReg32(reinterpret_cast<MMIO_PTR uint32_t*>(reinterpret_cast<MMIO_PTR char*>(regs_) +
kDevidleControl),
kDevidleControlRestoreRequired, 1, 0);
// Disable the controller.
RmwReg32(&regs_->i2c_en, kI2cEnEnable, 1, 0);
// Reconfigure the bus timing
if (bus_freq_ == kI2cMaxFastPlusSpeedHz) {
RmwReg32(&regs_->fs_scl_hcnt, 0, 16, fmp_scl_hcnt_);
RmwReg32(&regs_->fs_scl_lcnt, 0, 16, fmp_scl_lcnt_);
} else {
RmwReg32(&regs_->fs_scl_hcnt, 0, 16, fs_scl_hcnt_);
RmwReg32(&regs_->fs_scl_lcnt, 0, 16, fs_scl_lcnt_);
}
RmwReg32(&regs_->ss_scl_hcnt, 0, 16, ss_scl_hcnt_);
RmwReg32(&regs_->ss_scl_lcnt, 0, 16, ss_scl_lcnt_);
RmwReg32(&regs_->sda_hold, 0, 16, sda_hold_);
uint32_t speed = kCtlSpeedStandard;
if (bus_freq_ == kI2cMaxFastSpeedHz || bus_freq_ == kI2cMaxFastPlusSpeedHz) {
speed = kCtlSpeedFast;
}
MmioWrite32((0x1 << kCtlSlaveDisable) | (0x1 << kCtlRestartEnable) | (speed << kCtlSpeed) |
(kCtlMasterModeEnabled << kCtlMasterMode),
&regs_->ctl);
fbl::AutoLock lock(&irq_mask_mutex_);
// Mask all interrupts
MmioWrite32(0, &regs_->intr_mask);
if (MmioRead32(&regs_->comp_type) == kIntelDesignwareCompType) {
uint32_t param = MmioRead32(&regs_->comp_param1);
tx_fifo_depth_ = ExtractTxFifoDepthFromParam(param);
rx_fifo_depth_ = ExtractRxFifoDepthFromParam(param);
} else {
tx_fifo_depth_ = 8;
rx_fifo_depth_ = 8;
}
status = SetRxFifoThreshold(kDefaultRxFifoTriggerLevel);
if (status != ZX_OK) {
return status;
}
status = SetTxFifoThreshold(kDefaultTxFifoTriggerLevel);
if (status != ZX_OK) {
return status;
}
// Clear the signals
status = event_handle_.signal(
kRxFullSignal | kTxEmptySignal | kStopDetectedSignal | kErrorDetectedSignal, 0);
if (status != ZX_OK) {
return status;
}
// Reading this register clears all interrupts.
MmioRead32(&regs_->clr_intr);
// Unmask the interrupts we care about
MmioWrite32((1u << kIntrStopDetection) | (1u << kIntrTxAbort) | (1u << kIntrTxEmpty) |
(1u << kIntrTxOver) | (1u << kIntrRxFull) | (1u << kIntrRxOver) |
(1u << kIntrRxUnder),
&regs_->intr_mask);
return status;
}
zx_status_t IntelI2cController::DeviceSpecificInit(const uint16_t device_id) {
static const struct {
uint16_t device_ids[16];
// Offset of the soft reset register
size_t reset_offset;
// Internal controller frequency, in hertz
uint32_t controller_clock_frequency;
} dev_props[] = {
{
.device_ids =
{
INTEL_SUNRISE_POINT_SERIALIO_I2C0_DID,
INTEL_SUNRISE_POINT_SERIALIO_I2C1_DID,
INTEL_SUNRISE_POINT_SERIALIO_I2C2_DID,
INTEL_SUNRISE_POINT_SERIALIO_I2C3_DID,
INTEL_SUNRISE_POINT_SERIALIO_I2C4_DID,
},
.reset_offset = 0x204,
.controller_clock_frequency = 120 * 1000 * 1000,
},
{
.device_ids =
{
INTEL_WILDCAT_POINT_SERIALIO_I2C0_DID,
INTEL_WILDCAT_POINT_SERIALIO_I2C1_DID,
},
.reset_offset = 0x804,
.controller_clock_frequency = 100 * 1000 * 1000,
},
};
for (unsigned int i = 0; i < countof(dev_props); ++i) {
const unsigned int num_dev_ids = countof(dev_props[0].device_ids);
for (unsigned int dev_idx = 0; dev_idx < num_dev_ids; ++dev_idx) {
if (!dev_props[i].device_ids[dev_idx]) {
break;
}
if (dev_props[i].device_ids[dev_idx] != device_id) {
continue;
}
controller_freq_ = dev_props[i].controller_clock_frequency;
soft_reset_ = reinterpret_cast<MMIO_PTR uint32_t*>(reinterpret_cast<MMIO_PTR char*>(regs_) +
dev_props[i].reset_offset);
return ZX_OK;
}
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t IntelI2cController::AddSubordinates() {
// Try to fetch our metadata so that we know who is on the bus.
size_t metadata_size;
zx_status_t status = DdkGetMetadataSize(DEVICE_METADATA_ACPI_I2C_DEVICES, &metadata_size);
if ((status == ZX_ERR_NOT_FOUND) || ((status == ZX_OK) && !metadata_size)) {
// No metadata means that there are no devices on this bus. For now, we do
// nothing, but it might be a good idea to (someday) put the hardware into a
// low power state if we can, and perhaps even unload the driver at that
// point.
return ZX_OK;
}
if (status != ZX_OK) {
zxlogf(ERROR, "i2c: failed to fetch metadata size (status %d)", status);
return status;
}
if (metadata_size % sizeof(acpi_i2c_device_t)) {
zxlogf(ERROR, "i2c: metadata size %zu is not a multiple of device size %zu", metadata_size,
sizeof(acpi_i2c_device_t));
return ZX_ERR_INTERNAL;
}
size_t count = metadata_size / sizeof(acpi_i2c_device_t);
std::vector<acpi_i2c_device_t> devices(count);
status = DdkGetMetadata(DEVICE_METADATA_ACPI_I2C_DEVICES, devices.data(), metadata_size, nullptr);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c: failed to fetch metadata (status %d)", status);
return status;
}
uint32_t bus_speed = 0;
for (auto const& child : devices) {
zxlogf(DEBUG,
"i2c: got child[%zu] bus_controller=%d ten_bit=%d address=0x%x bus_speed=%u"
" protocol_id=0x%08x\n",
count--, child.is_bus_controller, child.ten_bit, child.address, child.bus_speed,
child.protocol_id);
if (bus_speed && bus_speed != child.bus_speed) {
zxlogf(ERROR, "i2c: cannot add devices with different bus speeds (%u, %u)", bus_speed,
child.bus_speed);
}
if (!bus_speed) {
SetBusFrequency(child.bus_speed);
bus_speed = child.bus_speed;
}
AddSubordinate(child.ten_bit ? kI2c10BitAddress : kI2c7BitAddress, child.address, child.props,
child.propcount);
}
return ZX_OK;
}
void IntelI2cController::DdkUnbind(ddk::UnbindTxn txn) {
zxlogf(INFO, "intel-i2c: unbind irq_handle %d irq_thread %lu", irq_handle_.get(), irq_thread_);
irq_handle_.destroy();
thrd_join(irq_thread_, nullptr);
txn.Reply();
}
void IntelI2cController::DdkRelease() { delete this; }
static zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = IntelI2cController::Create;
return ops;
}();
} // namespace intel_i2c
ZIRCON_DRIVER_BEGIN(intel_i2c, intel_i2c::driver_ops, "zircon", "0.1", 9)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI), BI_ABORT_IF(NE, BIND_PCI_VID, 0x8086),
BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_WILDCAT_POINT_SERIALIO_I2C0_DID),
BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_WILDCAT_POINT_SERIALIO_I2C1_DID),
BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C0_DID),
BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C1_DID),
BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C2_DID),
BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C3_DID),
BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C4_DID),
ZIRCON_DRIVER_END(intel_i2c)