blob: 0e5c51bf7a62a5a7a746e94d250656f66a3008a2 [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 <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/bcm-bus.h>
#include <ddk/protocol/display.h>
#include <ddk/protocol/platform-device.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/assert.h>
#include <bcm/bcm28xx.h>
#include <bcm/ioctl.h>
#define BCM_PROPERTY_TAG_GET_MACADDR (0x00010003)
#define BCM_MAILBOX_REQUEST (0x00000000)
#define MAILBOX_MMIO 0
// Preserve columns
// clang-format off
enum mailbox_channel {
ch_power = 0,
ch_framebuffer = 1,
ch_vuart = 2,
ch_vchic = 3,
ch_leds = 4,
ch_buttons = 5,
ch_touchscreen = 6,
ch_unused = 7,
ch_propertytags_tovc = 8,
ch_propertytags_fromvc = 9,
};
enum bcm_device {
bcm_dev_sd = 0,
bcm_dev_uart0 = 1,
bcm_dev_uart1 = 2,
bcm_dev_usb = 3,
bcm_dev_i2c0 = 4,
bcm_dev_i2c1 = 5,
bcm_dev_i2c2 = 6,
bcm_dev_spi = 7,
bcm_dev_ccp2tx = 8,
};
typedef struct {
uint32_t buff_size;
uint32_t code;
} property_tag_header_t;
typedef struct {
uint32_t tag;
uint32_t size;
uint32_t req;
uint8_t macid[8]; //note: this is a 6 byte request, but value buffers need to be 32-bit aligned
} property_tag_get_macid_t;
#define BCM_MAILBOX_TAG_GET_MACID {0x00010003,8,6,{0,0,0,0,0,0,0,0}}
typedef struct {
uint32_t tag;
uint32_t size;
uint32_t valsize;
uint32_t clockid;
uint32_t resp;
} property_tag_get_clock_rate_t;
#define BCM_MAILBOX_TAG_GET_CLOCKRATE {0x00030002,8,4,0,0}
typedef struct {
uint32_t tag;
} property_tag_endtag_t;
#define BCM_MAILBOX_TAG_ENDTAG {0x00000000}
// Must mmap memory on 4k page boundaries. The device doesn't exactly fall on
// a page boundary, so we align it to one.
#define PAGE_MASK_4K (~0xFFF)
#define MAILBOX_PAGE_ADDRESS ((ARMCTRL_0_SBM_BASE + 0x80) & PAGE_MASK_4K)
#define MAILBOX_PHYSICAL_ADDRESS (ARMCTRL_0_SBM_BASE + 0x80)
// The delta between the base of the page and the start of the device.
#define PAGE_REG_DELTA (MAILBOX_PHYSICAL_ADDRESS - MAILBOX_PAGE_ADDRESS)
// Offsets into the mailbox register for various operations.
#define MAILBOX_READ 0
#define MAILBOX_PEEK 2
#define MAILBOX_CONDIG 4
#define MAILBOX_STATUS 6
#define MAILBOX_WRITE 8
// Flags in the mailbox status register to signify state.
#define MAILBOX_FULL 0x80000000
#define MAILBOX_EMPTY 0x40000000
// Carve out 4k of device memory.
#define MAILBOX_REGS_LENGTH 0x1000
#define MAX_MAILBOX_READ_ATTEMPTS 8
#define MAILBOX_IO_DEADLINE_MS 1000
// clang-format on
static volatile uint32_t* mailbox_regs;
// All devices are initially turned off.
static uint32_t power_state = 0x0;
static zx_status_t mailbox_write(const enum mailbox_channel ch, uint32_t value) {
value = value | ch;
// Wait for there to be space in the FIFO.
zx_time_t deadline = zx_time_get(ZX_CLOCK_MONOTONIC) + ZX_MSEC(MAILBOX_IO_DEADLINE_MS);
while (mailbox_regs[MAILBOX_STATUS] & MAILBOX_FULL) {
if (zx_time_get(ZX_CLOCK_MONOTONIC) > deadline)
return ZX_ERR_TIMED_OUT;
}
// Write the value to the mailbox.
mailbox_regs[MAILBOX_WRITE] = value;
return ZX_OK;
}
static zx_status_t mailbox_read(enum mailbox_channel ch, uint32_t* result) {
assert(result);
uint32_t local_result = 0;
uint32_t attempts = 0;
do {
zx_time_t deadline = zx_time_get(ZX_CLOCK_MONOTONIC) + ZX_MSEC(MAILBOX_IO_DEADLINE_MS);
while (mailbox_regs[MAILBOX_STATUS] & MAILBOX_EMPTY) {
if (zx_time_get(ZX_CLOCK_MONOTONIC) > deadline)
return ZX_ERR_TIMED_OUT;
}
local_result = mailbox_regs[MAILBOX_READ];
attempts++;
} while ((((local_result)&0xF) != ch) && (attempts < MAX_MAILBOX_READ_ATTEMPTS));
// The bottom 4 bits represent the channel, shift those away and write the
// result into the ret parameter.
*result = (local_result >> 4);
return attempts < MAX_MAILBOX_READ_ATTEMPTS ? ZX_OK : ZX_ERR_IO;
}
// Use the Videocore to power on/off devices.
static zx_status_t bcm_vc_poweron(enum bcm_device dev) {
const uint32_t bit = 1 << dev;
zx_status_t ret = ZX_OK;
uint32_t new_power_state = power_state | bit;
if (new_power_state == power_state) {
// The VideoCore won't return an ACK if we try to enable a device that's
// already enabled, so we should terminate the control flow here.
return ZX_OK;
}
ret = mailbox_write(ch_power, new_power_state << 4);
if (ret != ZX_OK)
return ret;
// The Videocore must acknowledge a successful power on.
uint32_t ack = 0x0;
ret = mailbox_read(ch_power, &ack);
if (ret != ZX_OK)
return ret;
// Preserve the power state of the peripherals.
power_state = ack;
if (ack != new_power_state)
return ZX_ERR_IO;
return ZX_OK;
}
static zx_status_t bcm_get_property_tag(uint8_t* buf, const size_t len) {
zx_status_t ret = ZX_OK;
iotxn_t* txn;
property_tag_header_t header;
property_tag_endtag_t endtag = BCM_MAILBOX_TAG_ENDTAG;
header.buff_size = sizeof(header) + len + sizeof(endtag);
header.code = BCM_MAILBOX_REQUEST;
ret = iotxn_alloc(&txn, IOTXN_ALLOC_CONTIGUOUS | IOTXN_ALLOC_POOL, header.buff_size);
if (ret != 0)
return ret;
iotxn_physmap(txn);
ZX_DEBUG_ASSERT(txn->phys_count == 1);
zx_paddr_t phys = iotxn_phys(txn);
uint32_t offset = 0;
iotxn_copyto(txn, &header, sizeof(header), offset);
offset += sizeof(header);
iotxn_copyto(txn, buf, len, offset);
offset += len;
iotxn_copyto(txn, &endtag, sizeof(endtag), offset);
iotxn_cacheop(txn, IOTXN_CACHE_CLEAN, 0, header.buff_size);
ret = mailbox_write(ch_propertytags_tovc, (phys + BCM_SDRAM_BUS_ADDR_BASE));
if (ret != ZX_OK) {
goto cleanup_and_exit;
}
uint32_t ack = 0x0;
ret = mailbox_read(ch_propertytags_tovc, &ack);
if (ret != ZX_OK) {
goto cleanup_and_exit;
}
iotxn_cacheop(txn, IOTXN_CACHE_INVALIDATE, 0, header.buff_size);
iotxn_copyfrom(txn, buf, len, sizeof(header));
cleanup_and_exit:
iotxn_release(txn);
return ret;
}
static zx_status_t bcm_get_macid(void* ctx, uint8_t* mac) {
if (!mac) return ZX_ERR_INVALID_ARGS;
property_tag_get_macid_t tag = BCM_MAILBOX_TAG_GET_MACID;
zx_status_t ret = bcm_get_property_tag((uint8_t*)&tag, sizeof(tag));
memcpy(mac, tag.macid, 6);
return ret;
}
static zx_status_t bcm_get_clock_rate(void* ctx, const uint32_t clockid, uint32_t* res) {
if (!res) return ZX_ERR_INVALID_ARGS;
property_tag_get_clock_rate_t tag = BCM_MAILBOX_TAG_GET_CLOCKRATE;
tag.clockid = clockid;
zx_status_t ret = bcm_get_property_tag((uint8_t*)&tag, sizeof(tag));
// Make sure that we're getting data back for the clock that we requested.
if (tag.clockid != clockid) {
return ZX_ERR_IO;
}
// Fill in the return parameter;
*res = tag.resp;
return ret;
}
static zx_status_t mailbox_device_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) {
switch (op) {
case IOCTL_BCM_POWER_ON_USB:
return bcm_vc_poweron(bcm_dev_usb);
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
static zx_protocol_device_t mailbox_device_protocol = {
.version = DEVICE_OPS_VERSION,
.ioctl = mailbox_device_ioctl,
};
static zx_status_t bcm_set_framebuffer(void* ctx, zx_paddr_t addr) {
zx_status_t ret = mailbox_write(ch_framebuffer, addr + BCM_SDRAM_BUS_ADDR_BASE);
if (ret != ZX_OK)
return ret;
uint32_t ack = 0x0;
return mailbox_read(ch_framebuffer, &ack);
}
static bcm_bus_protocol_ops_t bus_protocol_ops = {
.get_macid = bcm_get_macid,
.get_clock_rate = bcm_get_clock_rate,
.set_framebuffer = bcm_set_framebuffer,
};
static zx_status_t mailbox_get_protocol(void* ctx, uint32_t proto_id, void* out) {
if (proto_id == ZX_PROTOCOL_BCM_BUS) {
bcm_bus_protocol_t* proto = out;
proto->ops = &bus_protocol_ops;
proto->ctx = ctx;
return ZX_OK;
} else {
return ZX_ERR_NOT_SUPPORTED;
}
}
static zx_status_t mailbox_add_gpios(void* ctx, uint32_t start, uint32_t count, uint32_t mmio_index,
const uint32_t* irqs, uint32_t irq_count) {
return ZX_ERR_NOT_SUPPORTED;
}
static pbus_interface_ops_t mailbox_bus_ops = {
.get_protocol = mailbox_get_protocol,
.add_gpios = mailbox_add_gpios,
};
static zx_status_t mailbox_bind(void* ctx, zx_device_t* parent, void** cookie) {
platform_device_protocol_t pdev;
if (device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &pdev) != ZX_OK) {
return ZX_ERR_NOT_SUPPORTED;
}
// Carve out some address space for the device -- it's memory mapped.
uintptr_t mmio_base;
size_t mmio_size;
zx_handle_t mmio_handle;
zx_status_t status = pdev_map_mmio(&pdev, MAILBOX_MMIO, ZX_CACHE_POLICY_UNCACHED_DEVICE,
(void **)&mmio_base, &mmio_size, &mmio_handle);
if (status != ZX_OK) {
printf("mailbox_bind pdev_map_mmio failed %d\n", status);
return status;
}
// The device is actually mapped at some offset into the page.
mailbox_regs = (uint32_t*)(mmio_base + PAGE_REG_DELTA);
device_add_args_t vc_rpc_args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "bcm-vc-rpc",
.ops = &mailbox_device_protocol,
// nothing should bind to this device
// all interaction will be done via the pbus_interface_t
.flags = DEVICE_ADD_NON_BINDABLE,
};
status = device_add(parent, &vc_rpc_args, NULL);
if (status != ZX_OK) {
zx_vmar_unmap(zx_vmar_root_self(), mmio_base, mmio_size);
zx_handle_close(mmio_handle);
return status;
}
bcm_vc_poweron(bcm_dev_sd);
bcm_vc_poweron(bcm_dev_usb);
bcm_vc_poweron(bcm_dev_i2c1);
pbus_interface_t intf;
intf.ops = &mailbox_bus_ops;
intf.ctx = NULL; // TODO(voydanoff) - add mailbox ctx struct
pdev_set_interface(&pdev, &intf);
return ZX_OK;
}
static zx_driver_ops_t bcm_mailbox_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = mailbox_bind,
};
ZIRCON_DRIVER_BEGIN(bcm_mailbox, bcm_mailbox_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_BROADCOMM),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_BROADCOMM_RPI3),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_BUS_IMPLEMENTOR_DID),
ZIRCON_DRIVER_END(bcm_mailbox)