| // 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-bus.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; |
| i2c_impl_protocol_t i2c; |
| 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; |
| } |
| |
| platform_bus_protocol_t pbus; |
| if ((status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_BUS, &pbus)) != ZX_OK) { |
| zxlogf(ERROR, "aml_i2c_bind: ZX_PROTOCOL_PLATFORM_BUS 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, |
| .flags = DEVICE_ADD_NON_BINDABLE, |
| }; |
| |
| status = device_add(parent, &args, &i2c->zxdev); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "aml_i2c_bind: device_add failed\n"); |
| goto fail; |
| } |
| |
| i2c->i2c.ops = &i2c_ops; |
| i2c->i2c.ctx = i2c; |
| pbus_register_protocol(&pbus, ZX_PROTOCOL_I2C_IMPL, &i2c->i2c, NULL); |
| |
| 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) |