blob: fee2a0165f1e4714c4d22596a6798c6c6a4740cf [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 <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/binding.h>
#include <ddk/protocol/platform-device.h>
#include <magenta/syscalls.h>
#include <magenta/threads.h>
#include <magenta/device/i2c.h>
#include <bcm/gpio.h>
#include <bcm/bcm28xx.h>
#include <bcm/ioctl.h>
#include "i2c.h"
#define BCM_MAX_SEGMENTS 20
#define BCM_FIFO_DEADLINE_MS 100
#define GPIO_MMIO 0
typedef struct {
mx_device_t* parent;
platform_device_protocol_t pdev_proto;
bcm_i2c_regs_t* control_regs;
uint32_t dev_id;
} bcm_i2c_t;
/* TODO - improve fifo read/write to be interrupt driven and capable of handling
multiple transactions in a buffer at once.
Right now we limit the transaction size to <= 16 bytes (fifo size)
and wait for transaction to complete before exiting. This allows
us to keep transactions easily framed. This driver is being written
to only support the PCM5121 codec at this point, so these limitations
are a reasonable tradeoff at this time.
*/
static mx_status_t bcm_write_fifo(bcm_i2c_t* ctx, uint8_t* data, uint32_t len){
if (len > BCM_BSC_FIFO_SIZE)
return MX_ERR_INVALID_ARGS;
ctx->control_regs->dlen = (uint32_t)len;
ctx->control_regs->control = BCM_BSC_CONTROL_ENABLE | BCM_BSC_CONTROL_START;
for (uint32_t i = 0; i < len; i++)
ctx->control_regs->fifo = data[i];
mx_time_t deadline = mx_time_get(MX_CLOCK_MONOTONIC) + MX_MSEC(BCM_FIFO_DEADLINE_MS);
while( !(ctx->control_regs->status & BCM_BSC_STATUS_DONE) ) {
if (mx_time_get(MX_CLOCK_MONOTONIC) > deadline) {
printf("FIFO write timed out\n");
return MX_ERR_TIMED_OUT;
}
}
if (ctx->control_regs->status & BCM_BSC_STATUS_ERR) {
ctx->control_regs->status |= (BCM_BSC_STATUS_ERR | BCM_BSC_STATUS_DONE);
return MX_ERR_TIMED_OUT;
}
ctx->control_regs->status |= BCM_BSC_STATUS_DONE; //clear the done status
return MX_OK;
}
static mx_status_t bcm_read_fifo(bcm_i2c_t* ctx, uint8_t* data, uint32_t len){
if (len > BCM_BSC_FIFO_SIZE)
return MX_ERR_INVALID_ARGS;
ctx->control_regs->dlen = (uint32_t)len;
ctx->control_regs->control = BCM_BSC_CONTROL_ENABLE | BCM_BSC_CONTROL_START |
BCM_BSC_CONTROL_READ;
mx_time_t deadline = mx_time_get(MX_CLOCK_MONOTONIC) + MX_MSEC(BCM_FIFO_DEADLINE_MS);
while( !(ctx->control_regs->status & BCM_BSC_STATUS_DONE) ){
if (mx_time_get(MX_CLOCK_MONOTONIC) > deadline){
printf("FIFO read timed out\n");
return MX_ERR_TIMED_OUT;
}
}
if (ctx->control_regs->status & BCM_BSC_STATUS_ERR) {
for (uint32_t i = 0; i < len; i++)
data[i] = (uint8_t)0;
ctx->control_regs->status |= (BCM_BSC_STATUS_ERR | BCM_BSC_STATUS_DONE);
return MX_ERR_TIMED_OUT;
}
ctx->control_regs->status |= BCM_BSC_STATUS_DONE; //clear the done status
for (uint32_t i = 0; i < len; i++)
data[i] = (uint8_t)ctx->control_regs->fifo;
return MX_OK;
}
static mx_status_t bcm_i2c_read(void* ctx, void* buf, size_t count, mx_off_t off, size_t* actual) {
bcm_i2c_t* i2c_ctx = ctx;
mx_status_t status = bcm_read_fifo(i2c_ctx,(uint8_t*)buf,(uint32_t)count);
if (status == MX_OK) {
*actual = count;
}
return status;
}
static mx_status_t bcm_i2c_write(void* ctx, const void* buf, size_t count, mx_off_t off, size_t* actual) {
bcm_i2c_t* i2c_ctx = ctx;
mx_status_t status = bcm_write_fifo(i2c_ctx,(uint8_t*)buf,(uint32_t)count);
if (status == MX_OK) {
*actual = count;
return MX_OK;
} else {
return -1;
}
}
static mx_status_t bcm_i2c_set_slave_addr(bcm_i2c_t* ctx, uint16_t address){
ctx->control_regs->slave_addr = (uint32_t)address;
return MX_OK;
}
static mx_status_t bcm_i2c_slave_transfer(bcm_i2c_t* ctx, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len) {
mx_status_t status = MX_OK;
uint32_t num_segments=0;
i2c_slave_ioctl_segment_t* segments = (i2c_slave_ioctl_segment_t*)in_buf;
// figure out how many segments are in this ioctl trnasfer
while ( (!(segments[num_segments].type == I2C_SEGMENT_TYPE_END)) & (num_segments < BCM_MAX_SEGMENTS)) {
num_segments++;
}
if (num_segments >= BCM_MAX_SEGMENTS)
return MX_ERR_INVALID_ARGS;
uint8_t* data =(uint8_t*)&segments[num_segments + 1]; // +1 to skip the end marker
uint32_t num_writes = 0;
uint32_t num_reads = 0;
for (uint32_t i = 0; i < num_segments; i++ ){
if (segments[i].type == I2C_SEGMENT_TYPE_WRITE) {
status = bcm_write_fifo(ctx, &data[num_writes], segments[i].len);
if (status != MX_OK)
return status;
num_writes += segments[i].len;
} else if (segments[i].type == I2C_SEGMENT_TYPE_READ) {
status = bcm_read_fifo(ctx, &((uint8_t*)out_buf)[num_reads], segments[i].len);
if (status != MX_OK)
return status;
num_reads += segments[i].len;
}
}
return MX_OK;
}
static mx_status_t bcm_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) {
bcm_i2c_t* i2c_ctx = ctx;
int ret;
switch (op) {
case IOCTL_I2C_BUS_ADD_SLAVE: {
const i2c_ioctl_add_slave_args_t* args = in_buf;
if (in_len < sizeof(*args))
return MX_ERR_INVALID_ARGS;
if (args->chip_address_width == 7) {
ret = bcm_i2c_set_slave_addr(i2c_ctx,args->chip_address);
} else {
return MX_ERR_INVALID_ARGS;
}
break;
}
case IOCTL_I2C_BUS_REMOVE_SLAVE: {
ret = MX_OK;
break;
}
case IOCTL_I2C_SLAVE_TRANSFER: {
ret = bcm_i2c_slave_transfer(i2c_ctx, in_buf, in_len, out_buf, out_len);
break;
}
case IOCTL_I2C_BUS_SET_FREQUENCY: {
ret = MX_OK;
break;
}
default:
return MX_ERR_INVALID_ARGS;
}
if (ret == MX_OK && out_len > 0 && out_actual) {
*out_actual = out_len;
}
return ret;
}
static mx_protocol_device_t i2c_device_proto = {
.version = DEVICE_OPS_VERSION,
.read = bcm_i2c_read,
.write = bcm_i2c_write,
.ioctl = bcm_i2c_ioctl,
};
static int i2c_bootstrap_thread(void *arg) {
assert(arg);
bcm_i2c_t* i2c_ctx = (bcm_i2c_t*)arg;
size_t mmio_size;
mx_handle_t mmio_handle = MX_HANDLE_INVALID;
mx_status_t status = pdev_map_mmio(&i2c_ctx->pdev_proto, i2c_ctx->dev_id,
MX_CACHE_POLICY_UNCACHED_DEVICE,
(void **)&i2c_ctx->control_regs, &mmio_size,
&mmio_handle);
if (status != MX_OK)
goto i2c_err;
i2c_ctx->control_regs->control = BCM_BSC_CONTROL_ENABLE | BCM_BSC_CONTROL_FIFO_CLEAR;
i2c_ctx->control_regs->clk_div = BCM_BSC_CLK_DIV_100K;
char id[5];
snprintf(id,sizeof(id),"i2c%u",i2c_ctx->dev_id);
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = id,
.ctx = i2c_ctx,
.ops = &i2c_device_proto,
};
status = device_add(i2c_ctx->parent, &args, NULL);
if (status == MX_OK) return 0;
i2c_err:
free(i2c_ctx);
return -1;
}
static mx_status_t bootstrap_i2c(mx_device_t* parent, platform_device_protocol_t* pdev_proto,
uint32_t dev_id) {
bcm_i2c_t* i2c_ctx = calloc(1, sizeof(*i2c_ctx));
if (!i2c_ctx)
return MX_ERR_NO_MEMORY;
i2c_ctx->parent = parent;
i2c_ctx->dev_id = dev_id;
memcpy(&i2c_ctx->pdev_proto, pdev_proto, sizeof(i2c_ctx->pdev_proto));
char tid[30];
snprintf(tid,sizeof(tid),"i2c%d_bootstrap_thread",dev_id);
thrd_t bootstrap_thrd;
int thrd_rc = thrd_create_with_name(&bootstrap_thrd,
i2c_bootstrap_thread, i2c_ctx, tid);
if (thrd_rc != thrd_success) {
free(i2c_ctx);
return thrd_status_to_mx_status(thrd_rc);
}
thrd_detach(bootstrap_thrd);
return MX_OK;
}
static mx_status_t i2c_bind(void* ctx, mx_device_t* parent, void** cookie) {
platform_device_protocol_t pdev;
mx_status_t ret = device_get_protocol(parent, MX_PROTOCOL_PLATFORM_DEV, &pdev);
if (ret != MX_OK) {
printf("i2c_bind can't find MX_PROTOCOL_PLATFORM_DEV\n");
return ret;
}
bcm_gpio_ctrl_t* gpio_regs;
// Carve out some address space for the device -- it's memory mapped.
size_t mmio_size;
mx_handle_t mmio_handle = MX_HANDLE_INVALID;
mx_status_t status = pdev_map_mmio(&pdev, GPIO_MMIO, MX_CACHE_POLICY_UNCACHED_DEVICE,
(void **)&gpio_regs, &mmio_size, &mmio_handle);
if (status != MX_OK) {
printf("i2c_bind: pdev_map_mmio failed: %d\n", status);
return status;
}
/* ALT Function 0 is I2C for these pins */
set_gpio_function(gpio_regs, BCM_SDA1_PIN, FSEL_ALT0);
set_gpio_function(gpio_regs, BCM_SCL1_PIN, FSEL_ALT0);
set_gpio_function(gpio_regs, BCM_SDA0_PIN, FSEL_ALT0);
set_gpio_function(gpio_regs, BCM_SCL0_PIN, FSEL_ALT0);
status = bootstrap_i2c(parent, &pdev, 0);
if (status != MX_OK) {
ret = status;
printf("Failed to initialize i2c0\n");
}
status = bootstrap_i2c(parent, &pdev, 1);
if (status != MX_OK) {
ret = status;
printf("Failed to initialize i2c1\n");
}
return ret;
}
static mx_driver_ops_t bcm_i2c_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = i2c_bind,
};
MAGENTA_DRIVER_BEGIN(bcm_i2c, bcm_i2c_driver_ops, "magenta", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, MX_PROTOCOL_PLATFORM_DEV),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_BROADCOMM),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_BROADCOMM_I2C),
MAGENTA_DRIVER_END(bcm_i2c)