| // 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-subordinate.h" |
| |
| #include <fuchsia/hardware/i2c/c/fidl.h> |
| #include <lib/fit/function.h> |
| #include <lib/zircon-internal/thread_annotations.h> |
| #include <lib/zx/clock.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <zircon/listnode.h> |
| #include <zircon/types.h> |
| |
| #include "intel-i2c-controller.h" |
| |
| namespace intel_i2c { |
| |
| // Time out after 2 seconds. |
| constexpr zx::duration kTimeout = zx::sec(2); |
| |
| // TODO We should be using interrupts during long operations, but |
| // the plumbing isn't all there for that apparently. |
| bool DoUntil(const fit::function<bool()>& condition, fit::function<void()> action, |
| const zx::duration poll_interval) { |
| const zx::time deadline = zx::deadline_after(kTimeout); |
| bool wait_for_condition_value; |
| while (!(wait_for_condition_value = condition())) { |
| zx::time now = zx::clock::get_monotonic(); |
| if (now >= deadline) |
| break; |
| if (poll_interval != zx::duration(0)) |
| zx::nanosleep(zx::deadline_after(poll_interval)); |
| action(); |
| } |
| return wait_for_condition_value; |
| } |
| |
| bool WaitFor(const fit::function<bool()>& condition, const zx::duration poll_interval) { |
| return DoUntil( |
| condition, [] {}, poll_interval); |
| } |
| |
| std::unique_ptr<IntelI2cSubordinate> IntelI2cSubordinate::Create( |
| IntelI2cController* controller, const uint8_t chip_address_width, const uint16_t chip_address, |
| const uint32_t i2c_class, const uint16_t vendor_id, const uint16_t device_id) { |
| if (chip_address_width != kI2c7BitAddress && chip_address_width != kI2c10BitAddress) { |
| zxlogf(ERROR, "Bad address width."); |
| return nullptr; |
| } |
| |
| return std::unique_ptr<IntelI2cSubordinate>(new IntelI2cSubordinate( |
| controller, chip_address_width, chip_address, i2c_class, vendor_id, device_id)); |
| } |
| |
| zx_status_t IntelI2cSubordinate::Transfer(const IntelI2cSubordinateSegment* segments, |
| int segment_count) { |
| zx_status_t status = ZX_OK; |
| int last_type = fuchsia_hardware_i2c_SegmentType_END; |
| |
| for (int i = 0; i < segment_count; i++) { |
| if (segments[i].type != fuchsia_hardware_i2c_SegmentType_READ && |
| segments[i].type != fuchsia_hardware_i2c_SegmentType_WRITE) { |
| status = ZX_ERR_INVALID_ARGS; |
| return status; |
| } |
| } |
| |
| if (!WaitFor(fit::bind_member(controller_, &IntelI2cController::IsBusIdle), zx::usec(50))) { |
| status = ZX_ERR_TIMED_OUT; |
| return status; |
| } |
| |
| const uint32_t ctl_addr_mode_bit = |
| (chip_address_width_ == kI2c7BitAddress) ? kCtlAddressingMode7Bit : kCtlAddressingMode10Bit; |
| const uint32_t tar_add_addr_mode_bit = |
| (chip_address_width_ == kI2c7BitAddress) ? kTarAddWidth7Bit : kTarAddWidth10Bit; |
| |
| controller_->SetAddressingMode(ctl_addr_mode_bit); |
| controller_->SetTargetAddress(tar_add_addr_mode_bit, chip_address_); |
| |
| controller_->Enable(); |
| |
| if (segment_count) |
| last_type = segments->type; |
| |
| while (segment_count--) { |
| int len = segments->len; |
| uint8_t* buf = segments->buf; |
| |
| // If this segment is in the same direction as the last, inject a |
| // restart at its start. |
| uint32_t restart = 0; |
| if (last_type == segments->type) |
| restart = 1; |
| size_t outstanding_reads = 0; |
| while (len-- || outstanding_reads) { |
| // Build the cmd register value. |
| uint32_t cmd = (restart << kDataCmdRestart); |
| restart = 0; |
| switch (segments->type) { |
| case fuchsia_hardware_i2c_SegmentType_WRITE: |
| // Wait if the TX FIFO is full |
| if (controller_->IsTxFifoFull()) { |
| status = controller_->WaitForTxEmpty(zx::deadline_after(kTimeout)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| cmd |= (*buf << kDataCmdDat); |
| cmd |= (kDataCmdCmdWrite << kDataCmdCmd); |
| buf++; |
| break; |
| case fuchsia_hardware_i2c_SegmentType_READ: |
| cmd |= (kDataCmdCmdRead << kDataCmdCmd); |
| break; |
| default: |
| // shouldn't be reachable |
| printf("invalid i2c segment type: %d\n", segments->type); |
| status = ZX_ERR_INVALID_ARGS; |
| return status; |
| } |
| |
| if (!len && !segment_count) { |
| cmd |= (0x1 << kDataCmdStop); |
| } |
| |
| if (segments->type == fuchsia_hardware_i2c_SegmentType_READ) { |
| status = controller_->IssueRx(cmd); |
| outstanding_reads++; |
| } else if (segments->type == fuchsia_hardware_i2c_SegmentType_WRITE) { |
| status = controller_->IssueTx(cmd); |
| } else { |
| __builtin_trap(); |
| } |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // If its a read then queue up more reads until we hit fifo_depth. |
| // (We use fifo_depth - 1 because going to the full fifo_depth |
| // causes an overflow interrupt). |
| if (outstanding_reads != 0 && len > 0 && |
| outstanding_reads < (size_t)(controller_->GetRxFifoDepth() - 1)) { |
| continue; |
| } |
| |
| uint32_t rx_data_left = controller_->GetRxFifoLevel(); |
| // If this is a read, extract data if it's ready. |
| while (outstanding_reads) { |
| while (rx_data_left == 0) { |
| // Make sure that the FIFO threshold will be crossed when |
| // the reads are ready. |
| uint32_t rx_threshold = static_cast<uint32_t>(outstanding_reads); |
| status = controller_->SetRxFifoThreshold(rx_threshold); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Clear the RX threshold signal |
| status = controller_->FlushRxFullIrq(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Wait for the FIFO to get some data. |
| status = controller_->WaitForRxFull(zx::deadline_after(kTimeout)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| rx_data_left = controller_->GetRxFifoLevel(); |
| } |
| |
| *buf = controller_->ReadRx(); |
| buf++; |
| outstanding_reads--; |
| rx_data_left--; |
| } |
| } |
| if (outstanding_reads != 0) { |
| __builtin_trap(); |
| } |
| |
| last_type = segments->type; |
| segments++; |
| } |
| |
| // Clear out the stop detect interrupt signal. |
| status = controller_->WaitForStopDetect(zx::deadline_after(kTimeout)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = controller_->ClearStopDetect(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if (!WaitFor(fit::bind_member(controller_, &IntelI2cController::IsBusIdle), zx::usec(50))) { |
| status = ZX_ERR_TIMED_OUT; |
| return status; |
| } |
| |
| // Read the data_cmd register to pull data out of the RX FIFO. |
| if (!DoUntil( |
| fit::bind_member(controller_, &IntelI2cController::IsRxFifoEmpty), |
| [this]() { controller_->ReadRx(); }, zx::duration(0))) { |
| status = ZX_ERR_TIMED_OUT; |
| return status; |
| } |
| |
| status = controller_->CheckForError(); |
| |
| return status; |
| } |
| |
| } // namespace intel_i2c |