blob: c9751eaef100dbe5cc3cf2391b69d2b8aae01a77 [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 <stdint.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <bits/limits.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/protocol/i2c-impl.h>
#include <ddk/protocol/platform-defs.h>
#include <ddk/protocol/platform-device.h>
#include <hw/reg.h>
#include <zircon/assert.h>
#include <zircon/types.h>
#define I2C_ERROR_SIGNAL ZX_USER_SIGNAL_0
#define I2C_TXN_COMPLETE_SIGNAL ZX_USER_SIGNAL_1
#define AML_I2C_CONTROL_REG_START (uint32_t)(1 << 0)
#define AML_I2C_CONTROL_REG_ACK_IGNORE (uint32_t)(1 << 1)
#define AML_I2C_CONTROL_REG_STATUS (uint32_t)(1 << 2)
#define AML_I2C_CONTROL_REG_ERR (uint32_t)(1 << 3)
#define AML_I2C_MAX_TRANSFER 256
typedef volatile struct {
uint32_t control;
uint32_t slave_addr;
uint32_t token_list_0;
uint32_t token_list_1;
uint32_t token_wdata_0;
uint32_t token_wdata_1;
uint32_t token_rdata_0;
uint32_t token_rdata_1;
} __PACKED aml_i2c_regs_t;
typedef enum {
TOKEN_END,
TOKEN_START,
TOKEN_SLAVE_ADDR_WR,
TOKEN_SLAVE_ADDR_RD,
TOKEN_DATA,
TOKEN_DATA_LAST,
TOKEN_STOP
} aml_i2c_token_t;
typedef struct {
zx_handle_t irq;
zx_handle_t event;
io_buffer_t regs_iobuff;
aml_i2c_regs_t* virt_regs;
zx_duration_t timeout;
} aml_i2c_dev_t;
typedef struct {
platform_device_protocol_t pdev;
zx_device_t* zxdev;
aml_i2c_dev_t* i2c_devs;
size_t dev_count;
} aml_i2c_t;
static zx_status_t aml_i2c_set_slave_addr(aml_i2c_dev_t *dev, uint16_t addr) {
addr &= 0x7f;
uint32_t reg = dev->virt_regs->slave_addr;
reg = reg & ~0xff;
reg = reg | ((addr << 1) & 0xff);
dev->virt_regs->slave_addr = reg;
return ZX_OK;
}
static int aml_i2c_irq_thread(void *arg) {
aml_i2c_dev_t *dev = arg;
zx_status_t status;
while (1) {
status = zx_interrupt_wait(dev->irq, NULL);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c: interrupt error\n");
continue;
}
uint32_t reg = dev->virt_regs->control;
if (reg & AML_I2C_CONTROL_REG_ERR) {
zx_object_signal(dev->event, 0, I2C_ERROR_SIGNAL);
zxlogf(ERROR,"i2c: error on bus\n");
} else {
zx_object_signal(dev->event, 0, I2C_TXN_COMPLETE_SIGNAL);
}
}
return ZX_OK;
}
static zx_status_t aml_i2c_dumpstate(aml_i2c_dev_t *dev) {
printf("control reg : %08x\n",dev->virt_regs->control);
printf("slave addr reg : %08x\n",dev->virt_regs->slave_addr);
printf("token list0 reg : %08x\n",dev->virt_regs->token_list_0);
printf("token list1 reg : %08x\n",dev->virt_regs->token_list_1);
printf("token wdata0 : %08x\n",dev->virt_regs->token_wdata_0);
printf("token wdata1 : %08x\n",dev->virt_regs->token_wdata_1);
printf("token rdata0 : %08x\n",dev->virt_regs->token_rdata_0);
printf("token rdata1 : %08x\n",dev->virt_regs->token_rdata_1);
return ZX_OK;
}
static zx_status_t aml_i2c_start_xfer(aml_i2c_dev_t *dev) {
//First have to clear the start bit before setting (RTFM)
dev->virt_regs->control &= ~AML_I2C_CONTROL_REG_START;
dev->virt_regs->control |= AML_I2C_CONTROL_REG_START;
return ZX_OK;
}
static zx_status_t aml_i2c_wait_event(aml_i2c_dev_t* dev, uint32_t sig_mask) {
zx_time_t deadline = zx_deadline_after(dev->timeout);
uint32_t observed;
sig_mask |= I2C_ERROR_SIGNAL;
zx_status_t status = zx_object_wait_one(dev->event, sig_mask, deadline, &observed);
if (status != ZX_OK) {
return status;
}
zx_object_signal(dev->event,observed,0);
if (observed & I2C_ERROR_SIGNAL)
return ZX_ERR_TIMED_OUT;
return ZX_OK;
}
static zx_status_t aml_i2c_write(aml_i2c_dev_t *dev, const uint8_t *buff, uint32_t len) {
ZX_DEBUG_ASSERT(len <= AML_I2C_MAX_TRANSFER);
uint32_t token_num = 0;
uint64_t token_reg = 0;
token_reg |= (uint64_t)TOKEN_START << (4*(token_num++));
token_reg |= (uint64_t)TOKEN_SLAVE_ADDR_WR << (4*(token_num++));
while (len > 0) {
bool is_last_iter = len <= 8;
uint32_t tx_size = is_last_iter ? len : 8;
for (uint32_t i=0; i < tx_size; i++) {
token_reg |= (uint64_t)TOKEN_DATA << (4*(token_num++));
}
if (is_last_iter) {
token_reg |= (uint64_t)TOKEN_STOP << (4*(token_num++));
}
dev->virt_regs->token_list_0 = (uint32_t)(token_reg & 0xffffffff);
dev->virt_regs->token_list_1 =
(uint32_t)((token_reg >> 32) & 0xffffffff);
uint64_t wdata = 0;
for (uint32_t i=0; i < tx_size; i++) {
wdata |= (uint64_t)buff[i] << (8*i);
}
dev->virt_regs->token_wdata_0 = (uint32_t)(wdata & 0xffffffff);
dev->virt_regs->token_wdata_1 = (uint32_t)((wdata >> 32) & 0xffffffff);
aml_i2c_start_xfer(dev);
//while (dev->virt_regs->control & 0x4) ;; // wait for idle
zx_status_t status = aml_i2c_wait_event(dev, I2C_TXN_COMPLETE_SIGNAL);
if (status != ZX_OK) {
return status;
}
len -= tx_size;
buff += tx_size;
token_num = 0;
token_reg = 0;
}
return ZX_OK;
}
static zx_status_t aml_i2c_read(aml_i2c_dev_t *dev, uint8_t *buff, uint32_t len) {
ZX_DEBUG_ASSERT(len <= AML_I2C_MAX_TRANSFER);
uint32_t token_num = 0;
uint64_t token_reg = 0;
token_reg |= (uint64_t)TOKEN_START << (4*(token_num++));
token_reg |= (uint64_t)TOKEN_SLAVE_ADDR_RD << (4*(token_num++));
while (len > 0) {
bool is_last_iter = len <= 8;
uint32_t rx_size = is_last_iter ? len : 8;
for (uint32_t i=0; i < (rx_size - 1); i++) {
token_reg |= (uint64_t)TOKEN_DATA << (4*(token_num++));
}
if (is_last_iter) {
token_reg |= (uint64_t)TOKEN_DATA_LAST << (4*(token_num++));
token_reg |= (uint64_t)TOKEN_STOP << (4*(token_num++));
} else {
token_reg |= (uint64_t)TOKEN_DATA << (4*(token_num++));
}
dev->virt_regs->token_list_0 = (uint32_t)(token_reg & 0xffffffff);
token_reg = token_reg >> 32;
dev->virt_regs->token_list_1 = (uint32_t)(token_reg);
//clear registers to prevent data leaking from last xfer
dev->virt_regs->token_rdata_0 = 0;
dev->virt_regs->token_rdata_1 = 0;
aml_i2c_start_xfer(dev);
zx_status_t status = aml_i2c_wait_event(dev, I2C_TXN_COMPLETE_SIGNAL);
if (status != ZX_OK) {
return status;
}
//while (dev->virt_regs->control & 0x4) ;; // wait for idle
uint64_t rdata;
rdata = dev->virt_regs->token_rdata_0;
rdata |= (uint64_t)(dev->virt_regs->token_rdata_1) << 32;
for (uint32_t i=0; i < sizeof(rdata); i++) {
buff[i] = (uint8_t)((rdata >> (8*i) & 0xff));
}
len -= rx_size;
buff += rx_size;
token_num = 0;
token_reg = 0;
}
return ZX_OK;
}
/* create instance of aml_i2c_t and do basic initialization. There will
be one of these instances for each of the soc i2c ports.
*/
static zx_status_t aml_i2c_dev_init(aml_i2c_t* i2c, unsigned index) {
aml_i2c_dev_t* device = &i2c->i2c_devs[index];
device->timeout = ZX_SEC(1);
zx_status_t status;
status = pdev_map_mmio_buffer(&i2c->pdev, index, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&device->regs_iobuff);
if (status != ZX_OK) {
zxlogf(ERROR, "aml_i2c_dev_init: pdev_map_mmio_buffer failed %d\n", status);
return status;
}
device->virt_regs = (aml_i2c_regs_t*)io_buffer_virt(&device->regs_iobuff);
status = pdev_map_interrupt(&i2c->pdev, index, &device->irq);
if (status != ZX_OK) {
return status;
}
status = zx_event_create(0, &device->event);
if (status != ZX_OK) {
return status;
}
thrd_t irqthrd;
thrd_create_with_name(&irqthrd, aml_i2c_irq_thread, device, "i2c_irq_thread");
return ZX_OK;
}
static uint32_t aml_i2c_get_bus_count(void* ctx) {
aml_i2c_t* i2c = ctx;
return i2c->dev_count;
}
static zx_status_t aml_i2c_get_max_transfer_size(void* ctx, uint32_t bus_id, size_t* out_size) {
*out_size = AML_I2C_MAX_TRANSFER;
return ZX_OK;
}
static zx_status_t aml_i2c_set_bitrate(void* ctx, uint32_t bus_id, uint32_t bitrate) {
// TODO(hollande,voydanoff) implement this
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t aml_i2c_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 && !write_length) {
return ZX_ERR_INVALID_ARGS;
}
aml_i2c_t* i2c = ctx;
if (bus_id >= i2c->dev_count) {
return ZX_ERR_INVALID_ARGS;
}
aml_i2c_dev_t *dev = &i2c->i2c_devs[bus_id];
zx_status_t status = aml_i2c_set_slave_addr(dev, address);
if (status != ZX_OK) {
return status;
}
if (write_length) {
status = aml_i2c_write(dev, write_buf, write_length);
if (status != ZX_OK) {
return status;
}
}
if (read_length) {
status = aml_i2c_read(dev, read_buf, read_length);
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
static i2c_impl_protocol_ops_t i2c_ops = {
.get_bus_count = aml_i2c_get_bus_count,
.get_max_transfer_size = aml_i2c_get_max_transfer_size,
.set_bitrate = aml_i2c_set_bitrate,
.transact = aml_i2c_transact,
};
static void aml_i2c_release(void* ctx) {
aml_i2c_t* i2c = ctx;
for (unsigned i = 0; i < i2c->dev_count; i++) {
aml_i2c_dev_t* device = &i2c->i2c_devs[i];
io_buffer_release(&device->regs_iobuff);
zx_handle_close(device->event);
zx_handle_close(device->irq);
}
free(i2c->i2c_devs);
free(i2c);
}
static zx_protocol_device_t i2c_device_proto = {
.version = DEVICE_OPS_VERSION,
.release = aml_i2c_release,
};
static zx_status_t aml_i2c_bind(void* ctx, zx_device_t* parent) {
zx_status_t status;
aml_i2c_t* i2c = calloc(1, sizeof(aml_i2c_t));
if (!i2c) {
return ZX_ERR_NO_MEMORY;
}
if ((status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &i2c->pdev)) != ZX_OK) {
zxlogf(ERROR, "aml_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, "aml_i2c_bind: pdev_get_device_info failed\n");
goto fail;
}
if (info.mmio_count != info.irq_count) {
zxlogf(ERROR, "aml_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(aml_i2c_dev_t));
if (!i2c->i2c_devs) {
goto fail;
}
i2c->dev_count = info.mmio_count;
for (unsigned i = 0; i < i2c->dev_count; i++) {
zx_status_t status = aml_i2c_dev_init(i2c, i);
if (status != ZX_OK) {
zxlogf(ERROR, "aml_i2c_bind: aml_i2c_dev_init failed: %d\n", status);
goto fail;
}
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "aml-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, "aml_i2c_bind: device_add failed\n");
goto fail;
}
return ZX_OK;
fail:
aml_i2c_release(i2c);
return status;
}
static zx_driver_ops_t aml_i2c_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = aml_i2c_bind,
};
ZIRCON_DRIVER_BEGIN(aml_i2c, aml_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_AMLOGIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_I2C),
ZIRCON_DRIVER_END(aml_i2c)