blob: 5ae43e953c1316bc102629ef905dc999be64a314 [file] [log] [blame]
// Copyright 2017 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 <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/io-buffer.h>
#include <ddk/protocol/i2c-impl.h>
#include <ddk/protocol/platform-defs.h>
#include <ddk/protocol/platform-device.h>
#include <hw/reg.h>
#include <lib/sync/completion.h>
#include <zircon/process.h>
#include <zircon/assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <threads.h>
#include "dw-i2c-regs.h"
typedef struct {
zx_handle_t irq_handle;
zx_handle_t event_handle;
io_buffer_t regs_iobuff;
void* virt_reg;
zx_duration_t timeout;
uint32_t tx_fifo_depth;
uint32_t rx_fifo_depth;
} i2c_dw_dev_t;
typedef struct {
platform_device_protocol_t pdev;
zx_device_t* zxdev;
i2c_dw_dev_t* i2c_devs;
size_t i2c_dev_count;
} i2c_dw_t;
static zx_status_t i2c_dw_read(i2c_dw_dev_t* dev, uint8_t *buff, uint32_t len);
static zx_status_t i2c_dw_write(i2c_dw_dev_t* dev, const uint8_t *buff, uint32_t len, bool stop);
static zx_status_t i2c_dw_set_slave_addr(i2c_dw_dev_t* dev, uint16_t addr);
zx_status_t i2c_dw_dumpstate(i2c_dw_dev_t* dev) {
zxlogf(INFO, "########################\n");
zxlogf(INFO, "%s\n", __FUNCTION__);
zxlogf(INFO, "########################\n");
zxlogf(INFO, "DW_I2C_ENABLE_STATUS = \t0x%x\n", I2C_DW_READ32(DW_I2C_ENABLE_STATUS));
zxlogf(INFO, "DW_I2C_ENABLE = \t0x%x\n", I2C_DW_READ32(DW_I2C_ENABLE));
zxlogf(INFO, "DW_I2C_CON = \t0x%x\n", I2C_DW_READ32(DW_I2C_CON));
zxlogf(INFO, "DW_I2C_TAR = \t0x%x\n", I2C_DW_READ32(DW_I2C_TAR));
zxlogf(INFO, "DW_I2C_HS_MADDR = \t0x%x\n", I2C_DW_READ32(DW_I2C_HS_MADDR));
zxlogf(INFO, "DW_I2C_SS_SCL_HCNT = \t0x%x\n", I2C_DW_READ32(DW_I2C_SS_SCL_HCNT));
zxlogf(INFO, "DW_I2C_SS_SCL_LCNT = \t0x%x\n", I2C_DW_READ32(DW_I2C_SS_SCL_LCNT));
zxlogf(INFO, "DW_I2C_FS_SCL_HCNT = \t0x%x\n", I2C_DW_READ32(DW_I2C_FS_SCL_HCNT));
zxlogf(INFO, "DW_I2C_FS_SCL_LCNT = \t0x%x\n", I2C_DW_READ32(DW_I2C_FS_SCL_LCNT));
zxlogf(INFO, "DW_I2C_INTR_MASK = \t0x%x\n", I2C_DW_READ32(DW_I2C_INTR_MASK));
zxlogf(INFO, "DW_I2C_RAW_INTR_STAT = \t0x%x\n", I2C_DW_READ32(DW_I2C_RAW_INTR_STAT));
zxlogf(INFO, "DW_I2C_RX_TL = \t0x%x\n", I2C_DW_READ32(DW_I2C_RX_TL));
zxlogf(INFO, "DW_I2C_TX_TL = \t0x%x\n", I2C_DW_READ32(DW_I2C_TX_TL));
zxlogf(INFO, "DW_I2C_STATUS = \t0x%x\n", I2C_DW_READ32(DW_I2C_STATUS));
zxlogf(INFO, "DW_I2C_TXFLR = \t0x%x\n", I2C_DW_READ32(DW_I2C_TXFLR));
zxlogf(INFO, "DW_I2C_RXFLR = \t0x%x\n", I2C_DW_READ32(DW_I2C_RXFLR));
zxlogf(INFO, "DW_I2C_COMP_PARAM_1 = \t0x%x\n", I2C_DW_READ32(DW_I2C_COMP_PARAM_1));
zxlogf(INFO, "DW_I2C_TX_ABRT_SOURCE = \t0x%x\n", I2C_DW_READ32(DW_I2C_TX_ABRT_SOURCE));
return ZX_OK;
}
static zx_status_t i2c_dw_enable_wait(i2c_dw_dev_t* dev, bool enable) {
int max_poll = 100;
int poll = 0;
// set enable bit to 0
I2C_DW_SET_BITS32(DW_I2C_ENABLE, DW_I2C_ENABLE_ENABLE_START, DW_I2C_ENABLE_ENABLE_BITS, enable);
do {
if (I2C_DW_GET_BITS32(DW_I2C_ENABLE_STATUS, DW_I2C_ENABLE_STATUS_EN_START,
DW_I2C_ENABLE_STATUS_EN_BITS) == enable) {
// we are done. exit
return ZX_OK;
}
// sleep 10 times the signaling period for the highest i2c transfer speed (400K) ~25uS
usleep(25);
} while (poll++ < max_poll);
zxlogf(ERROR, "%s: Could not %s I2C contoller! DW_I2C_ENABLE_STATUS = 0x%x\n",
__FUNCTION__,
enable? "enable" : "disable",
I2C_DW_READ32(DW_I2C_ENABLE_STATUS));
i2c_dw_dumpstate(dev);
return ZX_ERR_TIMED_OUT;
}
static zx_status_t i2c_dw_enable(i2c_dw_dev_t* dev) {
return i2c_dw_enable_wait(dev, I2C_ENABLE);
return ZX_OK;
}
static void i2c_dw_clear_intrrupts(i2c_dw_dev_t* dev) {
I2C_DW_READ32(DW_I2C_CLR_INTR); // reading this register will clear all the interrupts
}
static void i2c_dw_disable_interrupts(i2c_dw_dev_t* dev) {
I2C_DW_WRITE32(DW_I2C_INTR_MASK, 0);
}
static void i2c_dw_enable_interrupts(i2c_dw_dev_t* dev, uint32_t flag) {
I2C_DW_WRITE32(DW_I2C_INTR_MASK, flag);
}
static zx_status_t i2c_dw_disable(i2c_dw_dev_t* dev) {
return i2c_dw_enable_wait(dev, I2C_DISABLE);
}
static zx_status_t i2c_dw_wait_event(i2c_dw_dev_t* dev, uint32_t sig_mask) {
uint32_t observed;
zx_time_t deadline = zx_deadline_after(dev->timeout);
sig_mask |= I2C_ERROR_SIGNAL;
zx_status_t status = zx_object_wait_one(dev->event_handle, sig_mask, deadline, &observed);
if (status != ZX_OK) {
return status;
}
zx_object_signal(dev->event_handle, observed, 0);
if (observed & I2C_ERROR_SIGNAL) {
return ZX_ERR_TIMED_OUT;
}
return ZX_OK;
}
// Thread to handle interrupts
static int i2c_dw_irq_thread(void* arg) {
i2c_dw_dev_t* dev = (i2c_dw_dev_t*)arg;
zx_status_t status;
while (1) {
status = zx_interrupt_wait(dev->irq_handle, NULL);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: irq wait failed, retcode = %d\n", __FUNCTION__, status);
continue;
}
uint32_t reg = I2C_DW_READ32(DW_I2C_RAW_INTR_STAT);
if (reg & DW_I2C_INTR_TX_ABRT) {
// some sort of error has occured. figure it out
i2c_dw_dumpstate(dev);
zx_object_signal(dev->event_handle, 0, I2C_ERROR_SIGNAL);
zxlogf(ERROR, "i2c: error on bus\n");
} else {
zx_object_signal(dev->event_handle, 0, I2C_TXN_COMPLETE_SIGNAL);
}
i2c_dw_clear_intrrupts(dev);
i2c_dw_disable_interrupts(dev);
}
return ZX_OK;
}
static zx_status_t i2c_dw_transact(void* ctx, uint32_t bus_id, uint16_t address,
const void* write_buf, size_t write_length,
void* read_buf, size_t read_length) {
if (read_length > I2C_DW_MAX_TRANSFER || write_length > I2C_DW_MAX_TRANSFER) {
return ZX_ERR_OUT_OF_RANGE;
}
i2c_dw_t* i2c = ctx;
if (bus_id >= i2c->i2c_dev_count) {
return ZX_ERR_INVALID_ARGS;
}
i2c_dw_dev_t* dev = &i2c->i2c_devs[bus_id];
i2c_dw_set_slave_addr(dev, address);
i2c_dw_enable(dev);
i2c_dw_disable_interrupts(dev);
i2c_dw_clear_intrrupts(dev);
zx_status_t status = ZX_OK;
if (write_length > 0) {
status = i2c_dw_write(dev, write_buf, write_length, (read_length == 0));
}
if (status == ZX_OK && read_length > 0) {
status = i2c_dw_read(dev, read_buf, read_length);
}
i2c_dw_disable_interrupts(dev);
i2c_dw_clear_intrrupts(dev);
i2c_dw_disable(dev);
return status;
}
static zx_status_t i2c_dw_set_bitrate(void* ctx, uint32_t bus_id, uint32_t bitrate) {
// TODO: Can't implement due to lack of HI3660 documentation
return ZX_ERR_NOT_SUPPORTED;
}
static uint32_t i2c_dw_get_bus_count(void* ctx) {
i2c_dw_t* i2c = ctx;
return i2c->i2c_dev_count;
}
static zx_status_t i2c_dw_get_max_transfer_size(void* ctx, uint32_t bus_id, size_t* out_size) {
*out_size = I2C_DW_MAX_TRANSFER;
return ZX_OK;
}
static zx_status_t i2c_dw_set_slave_addr(i2c_dw_dev_t* dev, uint16_t addr) {
addr &= 0x7f; // support 7bit for now
uint32_t reg = I2C_DW_READ32(DW_I2C_TAR);
reg = I2C_DW_SET_MASK(reg, DW_I2C_TAR_TAR_START, DW_I2C_TAR_TAR_BITS, addr);
reg = I2C_DW_SET_MASK(reg, DW_I2C_TAR_10BIT_START, DW_I2C_TAR_10BIT_BITS, 0);
I2C_DW_WRITE32(DW_I2C_TAR, reg);
return ZX_OK;
}
static zx_status_t i2c_dw_read(i2c_dw_dev_t* dev, uint8_t *buff, uint32_t len) {
uint32_t rx_limit;
ZX_DEBUG_ASSERT(len <= I2C_DW_MAX_TRANSFER);
rx_limit = dev->rx_fifo_depth - I2C_DW_READ32(DW_I2C_RXFLR);
ZX_DEBUG_ASSERT(len <= rx_limit);
// set threshold to the number of bytes we want to read - 1
I2C_DW_SET_BITS32(DW_I2C_RX_TL, DW_I2C_RX_TL_START, DW_I2C_RX_TL_BITS, len-1);
while (len > 0) {
uint32_t cmd = 0;
if (len == 1) {
// send STOP cmd if last byte
cmd = I2C_DW_SET_MASK(cmd, DW_I2C_DATA_CMD_STOP_START, DW_I2C_DATA_CMD_STOP_BITS, 1);
}
I2C_DW_WRITE32(DW_I2C_DATA_CMD, cmd | (1 << DW_I2C_DATA_CMD_CMD_START));
len--;
}
i2c_dw_enable_interrupts(dev, DW_I2C_INTR_READ_INTR_MASK);
zx_status_t status = i2c_dw_wait_event(dev, I2C_TXN_COMPLETE_SIGNAL);
if (status != ZX_OK) {
return status;
}
uint32_t avail_read = I2C_DW_READ32(DW_I2C_RXFLR);
for (uint32_t i = 0; i < avail_read; i++) {
buff[i] = I2C_DW_GET_BITS32(DW_I2C_DATA_CMD, DW_I2C_DATA_CMD_DAT_START,
DW_I2C_DATA_CMD_DAT_BITS);
}
return ZX_OK;
}
static zx_status_t i2c_dw_write(i2c_dw_dev_t* dev, const uint8_t *buff, uint32_t len, bool stop) {
uint32_t tx_limit;
ZX_DEBUG_ASSERT(len <= I2C_DW_MAX_TRANSFER);
tx_limit = dev->tx_fifo_depth - I2C_DW_READ32(DW_I2C_TXFLR);
ZX_DEBUG_ASSERT(len <= tx_limit);
while (len > 0) {
uint32_t cmd = 0;
if (len == 1 && stop) {
// send STOP cmd if last byte
cmd = I2C_DW_SET_MASK(cmd, DW_I2C_DATA_CMD_STOP_START, DW_I2C_DATA_CMD_STOP_BITS, 1);
}
I2C_DW_WRITE32(DW_I2C_DATA_CMD, cmd | *buff++);
len--;
}
// at this point, we have to wait until all data has been transmitted.
i2c_dw_enable_interrupts(dev, DW_I2C_INTR_DEFAULT_INTR_MASK);
zx_status_t status = i2c_dw_wait_event(dev, I2C_TXN_COMPLETE_SIGNAL);
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
static zx_status_t i2c_dw_host_init(i2c_dw_dev_t* dev) {
uint32_t dw_comp_type;
uint32_t regval;
// Make sure we are truly running on a DesignWire IP
dw_comp_type = I2C_DW_READ32(DW_I2C_COMP_TYPE);
if (dw_comp_type != I2C_DW_COMP_TYPE_NUM) {
zxlogf(ERROR, "%s: Incompatible IP Block detected. Expected = 0x%x, Actual = 0x%x\n",
__FUNCTION__, I2C_DW_COMP_TYPE_NUM, dw_comp_type);
return ZX_ERR_NOT_SUPPORTED;
}
// read the various capabitlies of the component
dev->tx_fifo_depth = I2C_DW_GET_BITS32(DW_I2C_COMP_PARAM_1,
DW_I2C_COMP_PARAM_1_TXFIFOSZ_START,
DW_I2C_COMP_PARAM_1_TXFIFOSZ_BITS);
dev->rx_fifo_depth = I2C_DW_GET_BITS32(DW_I2C_COMP_PARAM_1,
DW_I2C_COMP_PARAM_1_RXFIFOSZ_START,
DW_I2C_COMP_PARAM_1_RXFIFOSZ_BITS);
/* I2C Block Initialization based on DW_apb_i2c_databook Section 7.3 */
// Disable I2C Block
i2c_dw_disable(dev);
// Configure the controller:
// - Slave Disable
regval = 0;
regval = I2C_DW_SET_MASK(regval, DW_I2C_CON_SLAVE_DIS_START,
DW_I2C_CON_SLAVE_DIS_BITS,
I2C_ENABLE);
// - Enable restart mode
regval = I2C_DW_SET_MASK(regval, DW_I2C_CON_RESTART_EN_START,
DW_I2C_CON_RESTART_EN_BITS,
I2C_ENABLE);
// - Set 7-bit address modeset
regval = I2C_DW_SET_MASK(regval, DW_I2C_CON_10BITADDRSLAVE_START,
DW_I2C_CON_10BITADDRSLAVE_BITS,
I2C_7BIT_ADDR);
regval = I2C_DW_SET_MASK(regval, DW_I2C_CON_10BITADDRMASTER_START,
DW_I2C_CON_10BITADDRMASTER_BITS,
I2C_7BIT_ADDR);
// - Set speed to fast, master enable
regval = I2C_DW_SET_MASK(regval, DW_I2C_CON_SPEED_START,
DW_I2C_CON_SPEED_BITS,
I2C_FAST_MODE);
// - Set master enable
regval = I2C_DW_SET_MASK(regval, DW_I2C_CON_MASTER_MODE_START,
DW_I2C_CON_MASTER_MODE_BITS,
I2C_ENABLE);
// write ifnal mask
I2C_DW_WRITE32(DW_I2C_CON, regval);
// Write SS/FS LCNT and HCNT
// FIXME: for now I am using the magical numbers from android source
I2C_DW_SET_BITS32(DW_I2C_SS_SCL_HCNT, DW_I2C_SS_SCL_HCNT_START, DW_I2C_SS_SCL_HCNT_BITS, 0x87);
I2C_DW_SET_BITS32(DW_I2C_SS_SCL_LCNT, DW_I2C_SS_SCL_LCNT_START, DW_I2C_SS_SCL_LCNT_BITS, 0x9f);
I2C_DW_SET_BITS32(DW_I2C_FS_SCL_HCNT, DW_I2C_FS_SCL_HCNT_START, DW_I2C_FS_SCL_HCNT_BITS, 0x1a);
I2C_DW_SET_BITS32(DW_I2C_FS_SCL_LCNT, DW_I2C_FS_SCL_LCNT_START, DW_I2C_FS_SCL_LCNT_BITS, 0x32);
// Setup TX FIFO Thresholds
I2C_DW_SET_BITS32(DW_I2C_TX_TL, DW_I2C_TX_TL_START, DW_I2C_TX_TL_BITS, 0);
// disable interrupts
i2c_dw_disable_interrupts(dev);
return ZX_OK;
}
static zx_status_t i2c_dw_init(i2c_dw_t* i2c, uint32_t index) {
zx_status_t status;
i2c_dw_dev_t* device = &i2c->i2c_devs[index];
device->timeout = ZX_SEC(10);
status = pdev_map_mmio_buffer(&i2c->pdev, index, ZX_CACHE_POLICY_UNCACHED_DEVICE, &device->regs_iobuff);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: io_buffer_init_physical failed %d\n", __FUNCTION__, status);
goto init_fail;
}
device->virt_reg = io_buffer_virt(&device->regs_iobuff);
status = pdev_map_interrupt(&i2c->pdev, index, &device->irq_handle);
if (status != ZX_OK) {
goto init_fail;
}
status = zx_event_create(0, &device->event_handle);
if (status != ZX_OK) {
goto init_fail;
}
// initialize i2c host controller
status = i2c_dw_host_init(device);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to initialize i2c host controller %d", __FUNCTION__, status);
goto init_fail;
}
thrd_t irq_thread;
thrd_create_with_name(&irq_thread, i2c_dw_irq_thread, device, "i2c_dw_irq_thread");
return ZX_OK;
init_fail:
if (device) {
io_buffer_release(&device->regs_iobuff);
if (device->event_handle != ZX_HANDLE_INVALID) {
zx_handle_close(device->event_handle);
}
if (device->irq_handle != ZX_HANDLE_INVALID) {
zx_handle_close(device->irq_handle);
}
free(device);
}
return status;
}
static i2c_impl_protocol_ops_t i2c_ops = {
.get_bus_count = i2c_dw_get_bus_count,
.get_max_transfer_size = i2c_dw_get_max_transfer_size,
.set_bitrate = i2c_dw_set_bitrate,
.transact = i2c_dw_transact,
};
static zx_protocol_device_t i2c_device_proto = {
.version = DEVICE_OPS_VERSION,
};
static zx_status_t dw_i2c_bind(void* ctx, zx_device_t* parent) {
printf("dw_i2c_bind\n");
zx_status_t status;
i2c_dw_t* i2c = calloc(1, sizeof(i2c_dw_t));
if (!i2c) {
return ZX_ERR_NO_MEMORY;
}
if ((status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &i2c->pdev)) != ZX_OK) {
zxlogf(ERROR, "dw_i2c_bind: ZX_PROTOCOL_PLATFORM_DEV not available\n");
goto fail;
}
pdev_device_info_t info;
status = pdev_get_device_info(&i2c->pdev, &info);
if (status != ZX_OK) {
zxlogf(ERROR, "dw_i2c_bind: pdev_get_device_info failed\n");
goto fail;
}
if (info.mmio_count != info.irq_count) {
zxlogf(ERROR, "dw_i2c_bind: mmio_count %u does not matchirq_count %u\n",
info.mmio_count, info.irq_count);
status = ZX_ERR_INVALID_ARGS;
goto fail;
}
i2c->i2c_devs = calloc(info.mmio_count, sizeof(i2c_dw_dev_t));
if (!i2c->i2c_devs) {
free(i2c);
return ZX_ERR_NO_MEMORY;
}
i2c->i2c_dev_count = info.mmio_count;
for (uint32_t i = 0; i < i2c->i2c_dev_count; i++) {
zx_status_t status = i2c_dw_init(i2c, i);
if (status != ZX_OK) {
zxlogf(ERROR, "dw_i2c_bind: dw_i2c_dev_init failed: %d\n", status);
goto fail;
}
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "dw-i2c",
.ctx = i2c,
.ops = &i2c_device_proto,
.proto_id = ZX_PROTOCOL_I2C_IMPL,
.proto_ops = &i2c_ops,
};
status = device_add(parent, &args, &i2c->zxdev);
if (status != ZX_OK) {
zxlogf(ERROR, "dw_i2c_bind: device_add failed\n");
goto fail;
}
return ZX_OK;
fail:
return status;
}
static zx_driver_ops_t dw_i2c_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = dw_i2c_bind,
};
ZIRCON_DRIVER_BEGIN(dw_i2c, dw_i2c_driver_ops, "zircon", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GENERIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_DW_I2C),
ZIRCON_DRIVER_END(dw_i2c)