blob: a1f5d85b5a4faad13c7ce6b6e3692d614a2f3f2a [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 <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;
}