| // 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 <assert.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/pci.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <hw/pci.h> |
| #include <intel-serialio/reg.h> |
| #include <intel-serialio/serialio.h> |
| #include <zircon/device/i2c.h> |
| #include <zircon/listnode.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
| |
| #include "controller.h" |
| #include "slave.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 |
| |
| // Implement the functionality of the i2c bus device. |
| |
| static uint32_t chip_addr_mask(int width) { |
| return ((1 << width) - 1); |
| } |
| |
| static zx_status_t intel_serialio_i2c_find_slave( |
| intel_serialio_i2c_slave_device_t** slave, |
| intel_serialio_i2c_device_t* device, uint16_t address) { |
| assert(slave); |
| |
| list_for_every_entry (&device->slave_list, *slave, |
| intel_serialio_i2c_slave_device_t, |
| slave_list_node) { |
| if ((*slave)->chip_address == address) |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| static zx_status_t intel_serialio_i2c_add_slave( |
| 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_slave_device_t* slave; |
| |
| mtx_lock(&device->mutex); |
| |
| // Make sure a slave with the given address doesn't already exist. |
| status = intel_serialio_i2c_find_slave(&slave, device, address); |
| if (status == ZX_OK) { |
| status = ZX_ERR_ALREADY_EXISTS; |
| } |
| if (status != ZX_ERR_NOT_FOUND) { |
| mtx_unlock(&device->mutex); |
| return status; |
| } |
| |
| slave = calloc(1, sizeof(*slave)); |
| if (!slave) { |
| status = ZX_ERR_NO_MEMORY; |
| mtx_unlock(&device->mutex); |
| return status; |
| } |
| slave->chip_address_width = width; |
| slave->chip_address = address; |
| slave->controller = device; |
| |
| list_add_head(&device->slave_list, &slave->slave_list_node); |
| mtx_unlock(&device->mutex); |
| |
| // Temporarily add binding support for the i2c slave. 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. |
| |
| // Retrieve pci_config (again) |
| pci_protocol_t pci; |
| status = device_get_protocol(device->pcidev, ZX_PROTOCOL_PCI, &pci); |
| if (status != ZX_OK) { |
| goto fail2; |
| } |
| |
| const pci_config_t* pci_config; |
| size_t config_size; |
| zx_handle_t config_handle; |
| status = pci_map_resource(&pci, PCI_RESOURCE_CONFIG, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| (void**)&pci_config, &config_size, &config_handle); |
| if (status != ZX_OK) { |
| xprintf("i2c: failed to map pci config: %d\n", status); |
| goto fail2; |
| } |
| |
| int count = 0; |
| slave->props[count++] = (zx_device_prop_t){BIND_PCI_VID, 0, pci_config->vendor_id}; |
| slave->props[count++] = (zx_device_prop_t){BIND_PCI_DID, 0, pci_config->device_id}; |
| slave->props[count++] = (zx_device_prop_t){BIND_I2C_ADDR, 0, address}; |
| |
| 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 = slave, |
| .ops = &intel_serialio_i2c_slave_device_proto, |
| .props = slave->props, |
| .prop_count = count, |
| }; |
| |
| status = device_add(device->zxdev, &args, &slave->zxdev); |
| if (status != ZX_OK) { |
| goto fail1; |
| } |
| |
| return ZX_OK; |
| |
| fail1: |
| zx_handle_close(config_handle); |
| fail2: |
| mtx_lock(&device->mutex); |
| list_delete(&slave->slave_list_node); |
| mtx_unlock(&device->mutex); |
| free(slave); |
| return status; |
| } |
| |
| static zx_status_t intel_serialio_i2c_remove_slave( |
| 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_slave_device_t* slave; |
| |
| mtx_lock(&device->mutex); |
| |
| // Find the slave we're trying to remove. |
| status = intel_serialio_i2c_find_slave(&slave, device, address); |
| if (status < 0) |
| goto remove_slave_finish; |
| if (slave->chip_address_width != width) { |
| xprintf("Chip address width mismatch.\n"); |
| status = ZX_ERR_NOT_FOUND; |
| goto remove_slave_finish; |
| } |
| |
| status = device_remove(slave->zxdev); |
| if (status < 0) |
| goto remove_slave_finish; |
| |
| list_delete(&slave->slave_list_node); |
| free(slave); |
| |
| remove_slave_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 zx_status_t intel_serialio_i2c_ioctl( |
| void* ctx, uint32_t op, const void* in_buf, size_t in_len, |
| void* out_buf, size_t out_len, size_t* out_actual) { |
| intel_serialio_i2c_device_t* device = ctx; |
| switch (op) { |
| case IOCTL_I2C_BUS_ADD_SLAVE: { |
| const i2c_ioctl_add_slave_args_t* args = in_buf; |
| if (in_len < sizeof(*args)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| return intel_serialio_i2c_add_slave(device, args->chip_address_width, |
| args->chip_address); |
| } |
| case IOCTL_I2C_BUS_REMOVE_SLAVE: { |
| const i2c_ioctl_remove_slave_args_t* args = in_buf; |
| if (in_len < sizeof(*args)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| return intel_serialio_i2c_remove_slave(device, args->chip_address_width, |
| args->chip_address); |
| } |
| case IOCTL_I2C_BUS_SET_FREQUENCY: { |
| const i2c_ioctl_set_bus_frequency_args_t* args = in_buf; |
| if (in_len < sizeof(*args)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return intel_serialio_i2c_set_bus_frequency(device, args->frequency); |
| } |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| 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); |
| if (status != ZX_OK) { |
| xprintf("i2c: error waiting for interrupt: %d\n", status); |
| continue; |
| } |
| |
| uint32_t intr_stat = *REG32(&dev->regs->intr_stat); |
| xprintf("Received i2c interrupt: %x %x\n", intr_stat, *REG32(&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); |
| *REG32(&dev->regs->clr_rx_under); |
| zxlogf(ERROR, "i2c: rx underflow detected!\n"); |
| } |
| 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); |
| *REG32(&dev->regs->clr_rx_over); |
| zxlogf(ERROR, "i2c: rx overflow detected!\n"); |
| } |
| 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); |
| *REG32(&dev->regs->clr_tx_over); |
| zxlogf(ERROR, "i2c: tx overflow detected!\n"); |
| } |
| 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\n", *REG32(&dev->regs->tx_abrt_source)); |
| zx_object_signal(dev->event_handle, 0, ERROR_DETECTED_SIGNAL); |
| *REG32(&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\n"); |
| } |
| if (intr_stat & (1u << INTR_STOP_DETECTION)) { |
| zx_object_signal(dev->event_handle, 0, STOP_DETECTED_SIGNAL); |
| *REG32(&dev->regs->clr_stop_det); |
| } |
| if (intr_stat & (1u << INTR_START_DETECTION)) { |
| *REG32(&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\n"); |
| } |
| |
| zx_interrupt_complete(dev->irq_handle); |
| } |
| 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) { |
| |
| *REG32(&controller->regs->data_cmd) = data_cmd; |
| return ZX_OK; |
| } |
| |
| zx_status_t intel_serialio_i2c_read_rx( |
| intel_serialio_i2c_device_t* controller, |
| uint8_t* data) { |
| |
| *data = *REG32(&controller->regs->data_cmd); |
| |
| uint32_t rx_tl; |
| intel_serialio_i2c_get_rx_fifo_threshold(controller, &rx_tl); |
| const uint32_t rxflr = *REG32(&controller->regs->rxflr) & 0x1ff; |
| // If we've dropped the RX queue level below the threshold, clear the signal |
| // and unmask the interrupt. |
| if (rxflr < rx_tl) { |
| 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; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t intel_serialio_i2c_issue_tx( |
| intel_serialio_i2c_device_t* controller, |
| uint32_t data_cmd) { |
| |
| *REG32(&controller->regs->data_cmd) = data_cmd; |
| uint32_t tx_tl; |
| intel_serialio_i2c_get_tx_fifo_threshold(controller, &tx_tl); |
| const uint32_t txflr = *REG32(&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 = (*REG32(&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 = (*REG32(&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_release(void* ctx) { |
| intel_serialio_i2c_device_t* dev = ctx; |
| zx_handle_close(dev->regs_handle); |
| zx_handle_close(dev->irq_handle); |
| zx_handle_close(dev->event_handle); |
| |
| // TODO: Handle joining the irq thread |
| free(dev); |
| } |
| |
| static zx_protocol_device_t intel_serialio_i2c_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .ioctl = intel_serialio_i2c_ioctl, |
| .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 (*REG32((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-- && |
| (*REG32((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; |
| } |
| |
| *REG32(&device->regs->ctl) = |
| (0x1 << CTL_SLAVE_DISABLE) | |
| (0x1 << CTL_RESTART_ENABLE) | |
| (speed << CTL_SPEED) | |
| (CTL_MASTER_MODE_ENABLED << CTL_MASTER_MODE); |
| |
| mtx_lock(&device->irq_mask_mutex); |
| // Mask all interrupts |
| *REG32(&device->regs->intr_mask) = 0; |
| |
| 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. |
| *REG32(&device->regs->clr_intr); |
| |
| // Unmask the interrupts we care about |
| *REG32(&device->regs->intr_mask) = (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); |
| |
| 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, |
| const pci_config_t* pci_config) { |
| |
| 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, |
| }, |
| .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, |
| }, |
| }; |
| |
| uint16_t device_id = pci_config->device_id; |
| |
| 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; |
| } |
| |
| void zx_status_t intel_serialio_add_devices(zx_device_t* parent, |
| pci_protocol_t* pci) { |
| // get child info from aux data |
| // space for 1 device plus header (more + cookie) |
| uint8_t childdata[sizeof(auxdata_i2c_device_t) + sizeof(uint32_t) * 2]; |
| size_t actual; |
| char args[16]; |
| uint32_t more; |
| uint32_t cookie = 0; |
| auxdata_i2c_device_t* child; |
| uint32_t bus_speed = 0; |
| // TODO: this seems nonstandard to device model |
| for (;;) { |
| memset(childdata, 0, sizeof(childdata)); |
| status = pci_get_auxdata(pci, "i2c-child", childdata, sizeof(childdata), |
| &actual); |
| if (status != ZX_OK) { |
| break; |
| } |
| if (actual != sizeof(childdata)) { |
| break; |
| } |
| more = *(uint32_t*)(childdata); |
| cookie = *(uint32_t*)(childdata + sizeof(uint32_t)); |
| child = (auxdata_i2c_device_t*)(childdata + 2 * sizeof(uint32_t)); |
| if (child->protocol_id == ZX_PROTOCOL_I2C_HID) { |
| if (bus_speed && bus_speed != child->bus_speed) { |
| zxlogf(ERROR, "i2c: cannot add devices with different bus speeds (%u, %u)\n", |
| bus_speed, child->bus_speed); |
| break; |
| } |
| if (!bus_speed) { |
| intel_serialio_i2c_set_bus_frequency(parent, child->bus_speed); |
| bus_speed = child->bus_speed; |
| } |
| intel_serialio_i2c_add_slave(parent, |
| child->ten_bit ? I2C_10BIT_ADDRESS : I2C_7BIT_ADDRESS, |
| child->address); |
| } |
| if (!more) { |
| break; |
| } |
| } |
| |
| } |
| |
| zx_status_t intel_serialio_bind_i2c(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->slave_list); |
| mtx_init(&device->mutex, mtx_plain); |
| mtx_init(&device->irq_mask_mutex, mtx_plain); |
| device->pcidev = dev; |
| |
| const pci_config_t* pci_config; |
| size_t config_size; |
| zx_handle_t config_handle; |
| zx_status_t status = pci_map_resource(&pci, PCI_RESOURCE_CONFIG, |
| ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| (void**)&pci_config, &config_size, |
| &config_handle); |
| if (status != ZX_OK) { |
| xprintf("i2c: failed to map pci config: %d\n", status); |
| goto fail; |
| } |
| |
| status = pci_map_resource(&pci, PCI_RESOURCE_BAR_0, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| (void**)&device->regs, &device->regs_size, &device->regs_handle); |
| if (status != ZX_OK) { |
| xprintf("i2c: failed to mape pci bar 0: %d\n", status); |
| goto fail; |
| } |
| |
| // set msi irq mode |
| status = pci_set_irq_mode(&pci, ZX_PCIE_IRQ_MODE_LEGACY, 1); |
| if (status < 0) { |
| xprintf("i2c: failed to set irq mode: %d\n", status); |
| goto fail; |
| } |
| |
| // get irq handle |
| status = pci_map_interrupt(&pci, 0, &device->irq_handle); |
| if (status != ZX_OK) { |
| xprintf("i2c: failed to get irq handle: %d\n", status); |
| goto fail; |
| } |
| |
| status = zx_event_create(0, &device->event_handle); |
| if (status != ZX_OK) { |
| xprintf("i2c: failed to create event handle: %d\n", 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) { |
| xprintf("i2c: failed to create irq thread: %d\n", 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, pci_config); |
| if (status < 0) |
| goto fail; |
| |
| status = intel_serialio_compute_bus_timing(device); |
| if (status < 0) |
| goto fail; |
| |
| // Temporary hack until we have routed through the FMCN ACPI tables. |
| if (pci_config->vendor_id == INTEL_VID && |
| pci_config->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 (pci_config->vendor_id == INTEL_VID && |
| pci_config->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; |
| } |
| |
| // 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) |
| goto fail; |
| |
| char name[ZX_DEVICE_NAME_MAX]; |
| snprintf(name, sizeof(name), "i2c-bus-%04x", pci_config->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) { |
| goto fail; |
| } |
| |
| xprintf( |
| "initialized intel serialio i2c driver, " |
| "reg=%p regsize=%ld\n", |
| device->regs, device->regs_size); |
| |
| intel_serialio_add_devices(device, &pci); |
| |
| zx_handle_close(config_handle); |
| return ZX_OK; |
| |
| fail: |
| // TODO: Handle joining the irq thread |
| if (device->regs_handle != ZX_HANDLE_INVALID) |
| zx_handle_close(device->regs_handle); |
| if (device->irq_handle != ZX_HANDLE_INVALID) |
| zx_handle_close(device->irq_handle); |
| if (device->event_handle != ZX_HANDLE_INVALID) |
| zx_handle_close(device->event_handle); |
| if (config_handle) |
| zx_handle_close(config_handle); |
| free(device); |
| |
| return status; |
| } |