| // 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/listnode.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/auxdata.h> |
| #include <ddk/protocol/i2c.h> |
| #include <ddk/protocol/pci.h> |
| #include <hw/pci.h> |
| #include <hw/reg.h> |
| |
| #include "binding.h" |
| #include "intel-i2c-subordinate.h" |
| |
| #define DEVIDLE_CONTROL 0x24c |
| #define DEVIDLE_CONTROL_CMD_IN_PROGRESS 0 |
| #define DEVIDLE_CONTROL_DEVIDLE 2 |
| #define DEVIDLE_CONTROL_RESTORE_REQUIRED 3 |
| |
| #define ACER_I2C_TOUCH INTEL_SUNRISE_POINT_SERIALIO_I2C1_DID |
| |
| // Number of entries at which the FIFO level triggers happen |
| #define DEFAULT_RX_FIFO_TRIGGER_LEVEL 8 |
| #define DEFAULT_TX_FIFO_TRIGGER_LEVEL 8 |
| |
| // Signals used on the controller's event_handle |
| #define RX_FULL_SIGNAL ZX_USER_SIGNAL_0 |
| #define TX_EMPTY_SIGNAL ZX_USER_SIGNAL_1 |
| #define STOP_DETECTED_SIGNAL ZX_USER_SIGNAL_2 |
| #define ERROR_DETECTED_SIGNAL ZX_USER_SIGNAL_3 |
| |
| // More than enough |
| #define MAX_TRANSFER_SIZE (UINT16_MAX - 1) |
| |
| #define INTEL_DESIGNWARE_COMP_TYPE 0x44570140 |
| |
| // Implement the functionality of the i2c bus device. |
| |
| static void intel_i2c_transact(void* ctx, const i2c_op_t ops[], size_t cnt, |
| i2c_transact_callback transact_cb, void* cookie) { |
| intel_serialio_i2c_subordinate_device_t* subordinate = ctx; |
| i2c_subordinate_segment_t segs[I2C_MAX_RW_OPS]; |
| if (cnt >= I2C_MAX_RW_OPS) { |
| transact_cb(cookie, ZX_ERR_NOT_SUPPORTED, NULL, 0); |
| return; |
| } |
| uint8_t* read_buffer = malloc(MAX_TRANSFER_SIZE); |
| if (read_buffer == NULL) { |
| zxlogf(ERROR, "intel-i2c-controller: out of memory"); |
| transact_cb(cookie, ZX_ERR_NO_MEMORY, NULL, 0); |
| return; |
| } |
| uint8_t* p_reads = read_buffer; |
| for (size_t i = 0; i < cnt; ++i) { |
| if (ops[i].is_read) { |
| segs[i].buf = p_reads; |
| segs[i].type = fuchsia_hardware_i2c_SegmentType_READ; |
| p_reads += ops[i].data_size; |
| if (p_reads - read_buffer > MAX_TRANSFER_SIZE) { |
| free(read_buffer); |
| transact_cb(cookie, ZX_ERR_INVALID_ARGS, NULL, 0); |
| return; |
| } |
| } else { |
| segs[i].buf = (void*)ops[i].data_buffer; |
| segs[i].type = fuchsia_hardware_i2c_SegmentType_WRITE; |
| } |
| segs[i].len = ops[i].data_size; |
| } |
| zx_status_t status = intel_serialio_i2c_subordinate_transfer(subordinate, segs, cnt); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "intel-i2c-controller: intel_serialio_i2c_subordinate_transfer: %d", status); |
| free(read_buffer); |
| transact_cb(cookie, status, NULL, 0); |
| return; |
| } |
| i2c_op_t read_ops[I2C_MAX_RW_OPS]; |
| size_t read_ops_cnt = 0; |
| p_reads = read_buffer; |
| for (size_t i = 0; i < cnt; ++i) { |
| if (ops[i].is_read) { |
| read_ops[read_ops_cnt] = ops[i]; |
| read_ops[read_ops_cnt].data_buffer = p_reads; |
| read_ops_cnt++; |
| p_reads += ops[i].data_size; |
| } |
| } |
| transact_cb(cookie, status, read_ops, read_ops_cnt); |
| free(read_buffer); |
| return; |
| } |
| |
| static zx_status_t intel_i2c_get_max_transfer_size(void* ctx, size_t* out_size) { |
| *out_size = MAX_TRANSFER_SIZE; |
| return ZX_OK; |
| } |
| |
| static uint32_t intel_i2c_extract_tx_fifo_depth_from_param(uint32_t param) { |
| return ((param >> 16) & 0xff) + 1; |
| } |
| |
| static uint32_t intel_i2c_extract_rx_fifo_depth_from_param(uint32_t param) { |
| return ((param >> 8) & 0xff) + 1; |
| } |
| |
| static zx_status_t intel_i2c_get_interrupt(void* ctx, uint32_t flags, zx_handle_t* out_handle) { |
| intel_serialio_i2c_subordinate_device_t* subordinate = ctx; |
| return intel_serialio_i2c_subordinate_get_irq(subordinate, out_handle); |
| } |
| |
| i2c_protocol_ops_t i2c_protocol_ops = { |
| .transact = intel_i2c_transact, |
| .get_max_transfer_size = intel_i2c_get_max_transfer_size, |
| .get_interrupt = intel_i2c_get_interrupt, |
| }; |
| |
| static uint32_t chip_addr_mask(int width) { return ((1 << width) - 1); } |
| |
| static zx_status_t intel_serialio_i2c_find_subordinate( |
| intel_serialio_i2c_subordinate_device_t** subordinate, intel_serialio_i2c_device_t* device, |
| uint16_t address) { |
| assert(subordinate); |
| |
| list_for_every_entry (&device->subordinate_list, *subordinate, |
| intel_serialio_i2c_subordinate_device_t, subordinate_list_node) { |
| if ((*subordinate)->chip_address == address) |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| static zx_status_t intel_serialio_i2c_add_subordinate(intel_serialio_i2c_device_t* device, |
| uint8_t width, uint16_t address, |
| uint32_t protocol_id, |
| zx_device_prop_t* moreprops, |
| uint32_t propcount) { |
| zx_status_t status; |
| |
| if ((width != I2C_7BIT_ADDRESS && width != I2C_10BIT_ADDRESS) || |
| (address & ~chip_addr_mask(width)) != 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| intel_serialio_i2c_subordinate_device_t* subordinate; |
| |
| mtx_lock(&device->mutex); |
| |
| // Make sure a subordinate with the given address doesn't already exist. |
| status = intel_serialio_i2c_find_subordinate(&subordinate, device, address); |
| if (status == ZX_OK) { |
| status = ZX_ERR_ALREADY_EXISTS; |
| } |
| if (status != ZX_ERR_NOT_FOUND) { |
| mtx_unlock(&device->mutex); |
| return status; |
| } |
| |
| subordinate = calloc(1, sizeof(*subordinate)); |
| if (!subordinate) { |
| status = ZX_ERR_NO_MEMORY; |
| mtx_unlock(&device->mutex); |
| return status; |
| } |
| subordinate->chip_address_width = width; |
| subordinate->chip_address = address; |
| subordinate->controller = device; |
| |
| list_add_head(&device->subordinate_list, &subordinate->subordinate_list_node); |
| mtx_unlock(&device->mutex); |
| |
| // Temporarily add binding support for the i2c subordinate. The real way to do |
| // this will involve ACPI/devicetree enumeration, but for now we publish PCI |
| // VID/DID and i2c ADDR as binding properties. |
| pci_protocol_t pci; |
| status = device_get_protocol(device->pcidev, ZX_PROTOCOL_PCI, &pci); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| uint16_t vendor_id; |
| uint16_t device_id; |
| int count = 0; |
| |
| pci_config_read16(&pci, PCI_CONFIG_VENDOR_ID, &vendor_id); |
| pci_config_read16(&pci, PCI_CONFIG_DEVICE_ID, &device_id); |
| |
| zx_device_prop_t props[8]; |
| if (countof(props) < 3 + propcount) { |
| zxlogf(ERROR, "i2c: subordinate at 0x%02x has too many props! (%u)", address, propcount); |
| status = ZX_ERR_INVALID_ARGS; |
| goto fail; |
| } |
| props[count++] = (zx_device_prop_t){BIND_PCI_VID, 0, vendor_id}; |
| props[count++] = (zx_device_prop_t){BIND_PCI_DID, 0, device_id}; |
| props[count++] = (zx_device_prop_t){BIND_TOPO_I2C, 0, BIND_TOPO_I2C_PACK(address)}; |
| memcpy(&props[count], moreprops, sizeof(zx_device_prop_t) * propcount); |
| count += propcount; |
| |
| char name[sizeof(address) * 2 + 2] = { |
| [sizeof(name) - 1] = '\0', |
| }; |
| snprintf(name, sizeof(name) - 1, "%04x", address); |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = name, |
| .ctx = subordinate, |
| .ops = &intel_serialio_i2c_subordinate_device_proto, |
| .proto_id = protocol_id, |
| .props = props, |
| .prop_count = count, |
| }; |
| |
| if (protocol_id == ZX_PROTOCOL_I2C) { |
| args.proto_ops = &i2c_protocol_ops; |
| } |
| |
| status = device_add(device->zxdev, &args, &subordinate->zxdev); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| return ZX_OK; |
| |
| fail: |
| mtx_lock(&device->mutex); |
| list_delete(&subordinate->subordinate_list_node); |
| mtx_unlock(&device->mutex); |
| free(subordinate); |
| return status; |
| } |
| |
| static zx_status_t intel_serialio_i2c_remove_subordinate(intel_serialio_i2c_device_t* device, |
| uint8_t width, uint16_t address) { |
| zx_status_t status; |
| |
| if ((width != I2C_7BIT_ADDRESS && width != I2C_10BIT_ADDRESS) || |
| (address & ~chip_addr_mask(width)) != 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| intel_serialio_i2c_subordinate_device_t* subordinate; |
| |
| mtx_lock(&device->mutex); |
| |
| // Find the subordinate we're trying to remove. |
| status = intel_serialio_i2c_find_subordinate(&subordinate, device, address); |
| if (status < 0) |
| goto remove_subordinate_finish; |
| if (subordinate->chip_address_width != width) { |
| zxlogf(ERROR, "Chip address width mismatch."); |
| status = ZX_ERR_NOT_FOUND; |
| goto remove_subordinate_finish; |
| } |
| |
| status = device_remove_deprecated(subordinate->zxdev); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| |
| list_delete(&subordinate->subordinate_list_node); |
| free(subordinate); |
| |
| remove_subordinate_finish: |
| mtx_unlock(&device->mutex); |
| return status; |
| } |
| |
| static uint32_t intel_serialio_compute_scl_hcnt(uint32_t controller_freq, uint32_t t_high_nanos, |
| 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; |
| } |
| |
| static uint32_t intel_serialio_compute_scl_lcnt(uint32_t controller_freq, uint32_t t_low_nanos, |
| 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; |
| } |
| |
| static zx_status_t intel_serialio_compute_bus_timing(intel_serialio_i2c_device_t* device) { |
| uint32_t clock_frequency = device->controller_freq; |
| |
| // These constants are from the i2c timing requirements |
| uint32_t fmp_hcnt = intel_serialio_compute_scl_hcnt(clock_frequency, 260, 120); |
| uint32_t fmp_lcnt = intel_serialio_compute_scl_lcnt(clock_frequency, 500, 120); |
| uint32_t fs_hcnt = intel_serialio_compute_scl_hcnt(clock_frequency, 600, 300); |
| uint32_t fs_lcnt = intel_serialio_compute_scl_lcnt(clock_frequency, 1300, 300); |
| uint32_t ss_hcnt = intel_serialio_compute_scl_hcnt(clock_frequency, 4000, 300); |
| uint32_t ss_lcnt = intel_serialio_compute_scl_lcnt(clock_frequency, 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; |
| } |
| |
| device->fmp_scl_hcnt = fmp_hcnt; |
| device->fmp_scl_lcnt = fmp_lcnt; |
| device->fs_scl_hcnt = fs_hcnt; |
| device->fs_scl_lcnt = fs_lcnt; |
| device->ss_scl_hcnt = ss_hcnt; |
| device->ss_scl_lcnt = ss_lcnt; |
| device->sda_hold = 1; |
| return ZX_OK; |
| } |
| |
| static zx_status_t intel_serialio_i2c_set_bus_frequency(intel_serialio_i2c_device_t* device, |
| uint32_t frequency) { |
| if (frequency != I2C_MAX_FAST_SPEED_HZ && frequency != I2C_MAX_STANDARD_SPEED_HZ && |
| frequency != I2C_MAX_FAST_PLUS_SPEED_HZ) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| mtx_lock(&device->mutex); |
| device->bus_freq = frequency; |
| |
| zx_status_t status = intel_serialio_i2c_reset_controller(device); |
| if (status != ZX_OK) { |
| mtx_unlock(&device->mutex); |
| return status; |
| } |
| |
| mtx_unlock(&device->mutex); |
| return ZX_OK; |
| } |
| |
| static int intel_serialio_i2c_irq_thread(void* arg) { |
| intel_serialio_i2c_device_t* dev = (intel_serialio_i2c_device_t*)arg; |
| zx_status_t status; |
| for (;;) { |
| status = zx_interrupt_wait(dev->irq_handle, NULL); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "i2c: error waiting for interrupt: %d", status); |
| break; |
| } |
| uint32_t intr_stat = readl(&dev->regs->intr_stat); |
| zxlogf(SPEW, "Received i2c interrupt: %x %x", intr_stat, readl(&dev->regs->raw_intr_stat)); |
| if (intr_stat & (1u << INTR_RX_UNDER)) { |
| // If we hit an underflow, it's a bug. |
| zx_object_signal(dev->event_handle, 0, ERROR_DETECTED_SIGNAL); |
| readl(&dev->regs->clr_rx_under); |
| zxlogf(ERROR, "i2c: rx underflow detected!"); |
| } |
| if (intr_stat & (1u << INTR_RX_OVER)) { |
| // If we hit an overflow, it's a bug. |
| zx_object_signal(dev->event_handle, 0, ERROR_DETECTED_SIGNAL); |
| readl(&dev->regs->clr_rx_over); |
| zxlogf(ERROR, "i2c: rx overflow detected!"); |
| } |
| if (intr_stat & (1u << INTR_RX_FULL)) { |
| mtx_lock(&dev->irq_mask_mutex); |
| zx_object_signal(dev->event_handle, 0, RX_FULL_SIGNAL); |
| RMWREG32(&dev->regs->intr_mask, INTR_RX_FULL, 1, 0); |
| mtx_unlock(&dev->irq_mask_mutex); |
| } |
| if (intr_stat & (1u << INTR_TX_OVER)) { |
| // If we hit an overflow, it's a bug. |
| zx_object_signal(dev->event_handle, 0, ERROR_DETECTED_SIGNAL); |
| readl(&dev->regs->clr_tx_over); |
| zxlogf(ERROR, "i2c: tx overflow detected!"); |
| } |
| if (intr_stat & (1u << INTR_TX_EMPTY)) { |
| mtx_lock(&dev->irq_mask_mutex); |
| zx_object_signal(dev->event_handle, 0, TX_EMPTY_SIGNAL); |
| RMWREG32(&dev->regs->intr_mask, INTR_TX_EMPTY, 1, 0); |
| mtx_unlock(&dev->irq_mask_mutex); |
| } |
| if (intr_stat & (1u << INTR_TX_ABORT)) { |
| zxlogf(ERROR, "i2c: tx abort detected: 0x%08x", readl(&dev->regs->tx_abrt_source)); |
| zx_object_signal(dev->event_handle, 0, ERROR_DETECTED_SIGNAL); |
| readl(&dev->regs->clr_tx_abort); |
| } |
| if (intr_stat & (1u << INTR_ACTIVITY)) { |
| // Should always be masked...remask it. |
| mtx_lock(&dev->irq_mask_mutex); |
| RMWREG32(&dev->regs->intr_mask, INTR_ACTIVITY, 1, 0); |
| mtx_unlock(&dev->irq_mask_mutex); |
| zxlogf(INFO, "i2c: spurious activity irq"); |
| } |
| if (intr_stat & (1u << INTR_STOP_DETECTION)) { |
| zx_object_signal(dev->event_handle, 0, STOP_DETECTED_SIGNAL); |
| readl(&dev->regs->clr_stop_det); |
| } |
| if (intr_stat & (1u << INTR_START_DETECTION)) { |
| readl(&dev->regs->clr_start_det); |
| } |
| if (intr_stat & (1u << INTR_GENERAL_CALL)) { |
| // Should always be masked...remask it. |
| mtx_lock(&dev->irq_mask_mutex); |
| RMWREG32(&dev->regs->intr_mask, INTR_GENERAL_CALL, 1, 0); |
| mtx_unlock(&dev->irq_mask_mutex); |
| zxlogf(INFO, "i2c: spurious general call irq"); |
| } |
| } |
| return 0; |
| } |
| |
| zx_status_t intel_serialio_i2c_wait_for_rx_full(intel_serialio_i2c_device_t* controller, |
| zx_time_t deadline) { |
| uint32_t observed; |
| zx_status_t status = zx_object_wait_one( |
| controller->event_handle, RX_FULL_SIGNAL | ERROR_DETECTED_SIGNAL, deadline, &observed); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (observed & ERROR_DETECTED_SIGNAL) { |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t intel_serialio_i2c_wait_for_tx_empty(intel_serialio_i2c_device_t* controller, |
| zx_time_t deadline) { |
| uint32_t observed; |
| zx_status_t status = zx_object_wait_one( |
| controller->event_handle, TX_EMPTY_SIGNAL | ERROR_DETECTED_SIGNAL, deadline, &observed); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (observed & ERROR_DETECTED_SIGNAL) { |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t intel_serialio_i2c_wait_for_stop_detect(intel_serialio_i2c_device_t* controller, |
| zx_time_t deadline) { |
| uint32_t observed; |
| zx_status_t status = zx_object_wait_one( |
| controller->event_handle, STOP_DETECTED_SIGNAL | ERROR_DETECTED_SIGNAL, deadline, &observed); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (observed & ERROR_DETECTED_SIGNAL) { |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t intel_serialio_i2c_check_for_error(intel_serialio_i2c_device_t* controller) { |
| uint32_t observed; |
| zx_status_t status = |
| zx_object_wait_one(controller->event_handle, ERROR_DETECTED_SIGNAL, 0, &observed); |
| if (status != ZX_OK && status != ZX_ERR_TIMED_OUT) { |
| return status; |
| } |
| if (observed & ERROR_DETECTED_SIGNAL) { |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t intel_serialio_i2c_clear_stop_detect(intel_serialio_i2c_device_t* controller) { |
| return zx_object_signal(controller->event_handle, STOP_DETECTED_SIGNAL, 0); |
| } |
| |
| // Perform a write to the DATA_CMD register, and clear |
| // interrupt masks as appropriate |
| zx_status_t intel_serialio_i2c_issue_rx(intel_serialio_i2c_device_t* controller, |
| uint32_t data_cmd) { |
| writel(data_cmd, &controller->regs->data_cmd); |
| return ZX_OK; |
| } |
| |
| zx_status_t intel_serialio_i2c_flush_rx_full_irq(intel_serialio_i2c_device_t* controller) { |
| mtx_lock(&controller->irq_mask_mutex); |
| zx_status_t status = zx_object_signal(controller->event_handle, RX_FULL_SIGNAL, 0); |
| RMWREG32(&controller->regs->intr_mask, INTR_RX_FULL, 1, 1); |
| mtx_unlock(&controller->irq_mask_mutex); |
| return status; |
| } |
| |
| zx_status_t intel_serialio_i2c_read_rx(intel_serialio_i2c_device_t* controller, uint8_t* data) { |
| *data = readl(&controller->regs->data_cmd); |
| return ZX_OK; |
| } |
| |
| zx_status_t intel_serialio_i2c_issue_tx(intel_serialio_i2c_device_t* controller, |
| uint32_t data_cmd) { |
| writel(data_cmd, &controller->regs->data_cmd); |
| uint32_t tx_tl; |
| intel_serialio_i2c_get_tx_fifo_threshold(controller, &tx_tl); |
| const uint32_t txflr = readl(&controller->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) { |
| mtx_lock(&controller->irq_mask_mutex); |
| zx_status_t status = zx_object_signal(controller->event_handle, TX_EMPTY_SIGNAL, 0); |
| RMWREG32(&controller->regs->intr_mask, INTR_TX_EMPTY, 1, 1); |
| mtx_unlock(&controller->irq_mask_mutex); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| void intel_serialio_i2c_get_rx_fifo_threshold(intel_serialio_i2c_device_t* controller, |
| uint32_t* threshold) { |
| *threshold = (readl(&controller->regs->rx_tl) & 0xff) + 1; |
| } |
| |
| // Get an RX interrupt whenever the RX FIFO size is >= the threshold. |
| zx_status_t intel_serialio_i2c_set_rx_fifo_threshold(intel_serialio_i2c_device_t* controller, |
| uint32_t threshold) { |
| if (threshold - 1 > UINT8_MAX) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| RMWREG32(&controller->regs->rx_tl, 0, 8, threshold - 1); |
| return ZX_OK; |
| } |
| |
| void intel_serialio_i2c_get_tx_fifo_threshold(intel_serialio_i2c_device_t* controller, |
| uint32_t* threshold) { |
| *threshold = (readl(&controller->regs->tx_tl) & 0xff) + 1; |
| } |
| |
| // Get a TX interrupt whenever the TX FIFO size is <= the threshold. |
| zx_status_t intel_serialio_i2c_set_tx_fifo_threshold(intel_serialio_i2c_device_t* controller, |
| uint32_t threshold) { |
| if (threshold - 1 > UINT8_MAX) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| RMWREG32(&controller->regs->tx_tl, 0, 8, threshold - 1); |
| return ZX_OK; |
| } |
| |
| static void intel_serialio_i2c_unbind(void* ctx) { |
| intel_serialio_i2c_device_t* dev = ctx; |
| if (dev) { |
| zxlogf(INFO, "intel-i2c: unbind irq_handle %d irq_thread %p", dev->irq_handle, |
| dev->irq_thread); |
| if ((dev->irq_handle != ZX_HANDLE_INVALID) && dev->irq_thread) { |
| zx_interrupt_destroy(dev->irq_handle); |
| thrd_join(dev->irq_thread, NULL); |
| } |
| if (dev->zxdev) { |
| device_remove_deprecated(dev->zxdev); |
| } |
| } |
| } |
| |
| static void intel_serialio_i2c_release(void* ctx) { |
| intel_serialio_i2c_device_t* dev = ctx; |
| if (dev) { |
| mmio_buffer_release(&dev->mmio); |
| zx_handle_close(dev->irq_handle); |
| zx_handle_close(dev->event_handle); |
| } |
| free(dev); |
| } |
| |
| static zx_protocol_device_t intel_serialio_i2c_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .unbind = intel_serialio_i2c_unbind, |
| .release = intel_serialio_i2c_release, |
| }; |
| |
| // The controller lock should already be held when entering this function. |
| zx_status_t intel_serialio_i2c_reset_controller(intel_serialio_i2c_device_t* device) { |
| zx_status_t status = ZX_OK; |
| |
| // The register will only return valid values if the ACPI _PS0 has been |
| // evaluated. |
| if (readl((void*)device->regs + DEVIDLE_CONTROL) != 0xffffffff) { |
| // Wake up device if it is in DevIdle state |
| RMWREG32((void*)device->regs + DEVIDLE_CONTROL, DEVIDLE_CONTROL_DEVIDLE, 1, 0); |
| |
| // Wait for wakeup to finish processing |
| int retry = 10; |
| while (retry-- && (readl((void*)device->regs + DEVIDLE_CONTROL) & |
| (1 << DEVIDLE_CONTROL_CMD_IN_PROGRESS))) { |
| usleep(10); |
| } |
| if (!retry) { |
| printf("i2c-controller: timed out waiting for device idle\n"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| } |
| |
| // Reset the device. |
| RMWREG32(device->soft_reset, 0, 2, 0x0); |
| RMWREG32(device->soft_reset, 0, 2, 0x3); |
| |
| // Clear the "Restore Required" flag |
| RMWREG32((void*)device->regs + DEVIDLE_CONTROL, DEVIDLE_CONTROL_RESTORE_REQUIRED, 1, 0); |
| |
| // Disable the controller. |
| RMWREG32(&device->regs->i2c_en, I2C_EN_ENABLE, 1, 0); |
| |
| // Reconfigure the bus timing |
| if (device->bus_freq == I2C_MAX_FAST_PLUS_SPEED_HZ) { |
| RMWREG32(&device->regs->fs_scl_hcnt, 0, 16, device->fmp_scl_hcnt); |
| RMWREG32(&device->regs->fs_scl_lcnt, 0, 16, device->fmp_scl_lcnt); |
| } else { |
| RMWREG32(&device->regs->fs_scl_hcnt, 0, 16, device->fs_scl_hcnt); |
| RMWREG32(&device->regs->fs_scl_lcnt, 0, 16, device->fs_scl_lcnt); |
| } |
| RMWREG32(&device->regs->ss_scl_hcnt, 0, 16, device->ss_scl_hcnt); |
| RMWREG32(&device->regs->ss_scl_lcnt, 0, 16, device->ss_scl_lcnt); |
| RMWREG32(&device->regs->sda_hold, 0, 16, device->sda_hold); |
| |
| unsigned int speed = CTL_SPEED_STANDARD; |
| if (device->bus_freq == I2C_MAX_FAST_SPEED_HZ || device->bus_freq == I2C_MAX_FAST_PLUS_SPEED_HZ) { |
| speed = CTL_SPEED_FAST; |
| } |
| |
| writel((0x1 << CTL_SLAVE_DISABLE) | (0x1 << CTL_RESTART_ENABLE) | (speed << CTL_SPEED) | |
| (CTL_MASTER_MODE_ENABLED << CTL_MASTER_MODE), |
| &device->regs->ctl); |
| |
| mtx_lock(&device->irq_mask_mutex); |
| // Mask all interrupts |
| writel(0, &device->regs->intr_mask); |
| |
| if (readl(&device->regs->comp_type) == INTEL_DESIGNWARE_COMP_TYPE) { |
| uint32_t param = readl(&device->regs->comp_param1); |
| device->tx_fifo_depth = intel_i2c_extract_tx_fifo_depth_from_param(param); |
| device->rx_fifo_depth = intel_i2c_extract_rx_fifo_depth_from_param(param); |
| } else { |
| device->tx_fifo_depth = 8; |
| device->rx_fifo_depth = 8; |
| } |
| |
| status = intel_serialio_i2c_set_rx_fifo_threshold(device, DEFAULT_RX_FIFO_TRIGGER_LEVEL); |
| if (status != ZX_OK) { |
| goto cleanup; |
| } |
| status = intel_serialio_i2c_set_tx_fifo_threshold(device, DEFAULT_TX_FIFO_TRIGGER_LEVEL); |
| if (status != ZX_OK) { |
| goto cleanup; |
| } |
| |
| // Clear the signals |
| status = zx_object_signal( |
| device->event_handle, |
| RX_FULL_SIGNAL | TX_EMPTY_SIGNAL | STOP_DETECTED_SIGNAL | ERROR_DETECTED_SIGNAL, 0); |
| if (status != ZX_OK) { |
| goto cleanup; |
| } |
| |
| // Reading this register clears all interrupts. |
| readl(&device->regs->clr_intr); |
| |
| // Unmask the interrupts we care about |
| writel((1u << INTR_STOP_DETECTION) | (1u << INTR_TX_ABORT) | (1u << INTR_TX_EMPTY) | |
| (1u << INTR_TX_OVER) | (1u << INTR_RX_FULL) | (1u << INTR_RX_OVER) | |
| (1u << INTR_RX_UNDER), |
| &device->regs->intr_mask); |
| |
| cleanup: |
| mtx_unlock(&device->irq_mask_mutex); |
| return status; |
| } |
| |
| static zx_status_t intel_serialio_i2c_device_specific_init(intel_serialio_i2c_device_t* device, |
| 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; |
| } |
| |
| device->controller_freq = dev_props[i].controller_clock_frequency; |
| device->soft_reset = (void*)device->regs + dev_props[i].reset_offset; |
| return ZX_OK; |
| } |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static void intel_serialio_add_devices(intel_serialio_i2c_device_t* parent, pci_protocol_t* pci) { |
| // get child info from aux data, max 4 |
| // TODO: this seems nonstandard to device model |
| auxdata_i2c_device_t childdata[4]; |
| memset(childdata, 0, sizeof(childdata)); |
| |
| size_t actual; |
| zx_status_t status = pci_get_auxdata(pci, "i2c-child", childdata, sizeof(childdata), &actual); |
| if (status != ZX_OK) { |
| return; |
| } |
| |
| auxdata_i2c_device_t* child = &childdata[0]; |
| uint32_t count = actual / sizeof(auxdata_i2c_device_t); |
| uint32_t bus_speed = 0; |
| while (count--) { |
| zxlogf(TRACE, |
| "i2c: got child[%u] 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) { |
| intel_serialio_i2c_set_bus_frequency(parent, child->bus_speed); |
| bus_speed = child->bus_speed; |
| } |
| intel_serialio_i2c_add_subordinate( |
| parent, child->ten_bit ? I2C_10BIT_ADDRESS : I2C_7BIT_ADDRESS, child->address, |
| child->protocol_id, child->props, child->propcount); |
| child += 1; |
| } |
| } |
| |
| zx_status_t intel_i2c_bind(void* ctx, zx_device_t* dev) { |
| pci_protocol_t pci; |
| if (device_get_protocol(dev, ZX_PROTOCOL_PCI, &pci)) |
| return ZX_ERR_NOT_SUPPORTED; |
| |
| intel_serialio_i2c_device_t* device = calloc(1, sizeof(*device)); |
| if (!device) |
| return ZX_ERR_NO_MEMORY; |
| |
| list_initialize(&device->subordinate_list); |
| mtx_init(&device->mutex, mtx_plain); |
| mtx_init(&device->irq_mask_mutex, mtx_plain); |
| device->pcidev = dev; |
| |
| uint16_t vendor_id; |
| uint16_t device_id; |
| pci_config_read16(&pci, PCI_CONFIG_VENDOR_ID, &vendor_id); |
| pci_config_read16(&pci, PCI_CONFIG_DEVICE_ID, &device_id); |
| |
| zx_status_t status = pci_map_bar_buffer(&pci, 0u, ZX_CACHE_POLICY_UNCACHED_DEVICE, &device->mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "i2c: failed to mape pci bar 0: %d", status); |
| goto fail; |
| } |
| device->regs = device->mmio.vaddr; |
| |
| // set msi irq mode |
| status = pci_set_irq_mode(&pci, ZX_PCIE_IRQ_MODE_LEGACY, 1); |
| if (status < 0) { |
| zxlogf(ERROR, "i2c: failed to set irq mode: %d", status); |
| goto fail; |
| } |
| |
| // get irq handle |
| status = pci_map_interrupt(&pci, 0, &device->irq_handle); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "i2c: failed to get irq handle: %d", status); |
| goto fail; |
| } |
| |
| status = zx_event_create(0, &device->event_handle); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "i2c: failed to create event handle: %d", status); |
| goto fail; |
| } |
| |
| // start irq thread |
| int ret = |
| thrd_create_with_name(&device->irq_thread, intel_serialio_i2c_irq_thread, device, "i2c-irq"); |
| if (ret != thrd_success) { |
| zxlogf(ERROR, "i2c: failed to create irq thread: %d", ret); |
| goto fail; |
| } |
| |
| // Run the bus at standard speed by default. |
| device->bus_freq = I2C_MAX_STANDARD_SPEED_HZ; |
| |
| status = intel_serialio_i2c_device_specific_init(device, device_id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "i2c: device specific init failed: %d", status); |
| goto fail; |
| } |
| |
| status = intel_serialio_compute_bus_timing(device); |
| if (status < 0) { |
| zxlogf(ERROR, "i2c: compute bus timing failed: %d", status); |
| goto fail; |
| } |
| |
| // 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. |
| device->fmp_scl_lcnt = 0x0042; |
| device->fmp_scl_hcnt = 0x001b; |
| device->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. |
| device->fs_scl_lcnt = 0x00b6; |
| device->fs_scl_hcnt = 0x0059; |
| device->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. |
| device->fs_scl_lcnt = 0x00ba; |
| device->fs_scl_hcnt = 0x005d; |
| device->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. |
| device->fs_scl_lcnt = 0x005a; |
| device->fs_scl_hcnt = 0x00a6; |
| device->sda_hold = 0x24; |
| } |
| |
| // Configure the I2C controller. We don't need to hold the lock because |
| // nobody else can see this controller yet. |
| status = intel_serialio_i2c_reset_controller(device); |
| if (status < 0) { |
| zxlogf(ERROR, "i2c: reset controller failed: %d", status); |
| goto fail; |
| } |
| |
| char name[ZX_DEVICE_NAME_MAX]; |
| snprintf(name, sizeof(name), "i2c-bus-%04x", device_id); |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = name, |
| .ctx = device, |
| .ops = &intel_serialio_i2c_device_proto, |
| }; |
| |
| status = device_add(dev, &args, &device->zxdev); |
| if (status < 0) { |
| zxlogf(ERROR, "device add failed: %d", status); |
| goto fail; |
| } |
| |
| zxlogf(INFO, |
| "initialized intel serialio i2c driver, " |
| "reg=%p regsize=%ld\n", |
| device->regs, device->mmio.size); |
| |
| intel_serialio_add_devices(device, &pci); |
| return ZX_OK; |
| |
| fail: |
| intel_serialio_i2c_unbind(device); |
| intel_serialio_i2c_release(device); |
| return status; |
| } |
| |
| static zx_driver_ops_t intel_i2c_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = intel_i2c_bind, |
| }; |
| |
| 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) |