blob: 31fedf50ea6bfc0bf88034e0d62e7a41a4a8c74f [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/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/binding.h>
#include <ddk/protocol/platform-defs.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "platform-bus.h"
#include "platform-proxy.h"
static i2c_txn_t* get_i2c_txn(platform_bus_t* bus, pdev_req_t* req, zx_handle_t channel) {
mtx_lock(&bus->i2c_txn_lock);
i2c_txn_t* txn = list_remove_head_type(&bus->i2c_txns, i2c_txn_t, node);
mtx_unlock(&bus->i2c_txn_lock);
if (!txn) {
txn = malloc(sizeof(i2c_txn_t));
if (!txn) {
return NULL;
}
}
txn->bus = bus;
txn->txid = req->txid;
txn->complete_cb = req->i2c.txn_ctx.complete_cb;
txn->cookie = req->i2c.txn_ctx.cookie;
txn->channel = channel;
return txn;
}
static void put_i2c_txn(i2c_txn_t* txn) {
platform_bus_t* bus = txn->bus;
mtx_lock(&bus->i2c_txn_lock);
list_add_tail(&bus->i2c_txns, &txn->node);
mtx_unlock(&bus->i2c_txn_lock);
}
static zx_status_t platform_dev_get_mmio(platform_dev_t* dev, uint32_t index,
zx_handle_t* out_handle, uint32_t* out_handle_count) {
if (index >= dev->mmio_count) {
return ZX_ERR_INVALID_ARGS;
}
pbus_mmio_t* mmio = &dev->mmios[index];
zx_status_t status = zx_vmo_create_physical(dev->bus->resource, mmio->base, mmio->length,
out_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_map_mmio: zx_vmo_create_physical failed %d\n", status);
return status;
}
*out_handle_count = 1;
return ZX_OK;
}
static zx_status_t platform_dev_get_interrupt(platform_dev_t* dev, uint32_t index,
zx_handle_t* out_handle, uint32_t* out_handle_count) {
if (index >= dev->irq_count || !out_handle) {
return ZX_ERR_INVALID_ARGS;
}
pbus_irq_t* irq = &dev->irqs[index];
zx_status_t status = zx_interrupt_create(dev->bus->resource, irq->irq, ZX_INTERRUPT_REMAP_IRQ, out_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_get_interrupt: zx_interrupt_create failed %d\n", status);
return status;
}
*out_handle_count = 1;
return ZX_OK;
}
static zx_status_t platform_dev_ums_get_initial_mode(platform_dev_t* dev, usb_mode_t* out_mode) {
platform_bus_t* bus = dev->bus;
if (!bus->ums.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
return usb_mode_switch_get_initial_mode(&bus->ums, out_mode);
}
static zx_status_t platform_dev_ums_set_mode(platform_dev_t* dev, usb_mode_t mode) {
platform_bus_t* bus = dev->bus;
if (!bus->ums.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
return usb_mode_switch_set_mode(&bus->ums, mode);
}
static zx_status_t platform_dev_gpio_config(platform_dev_t* dev, uint32_t index,
gpio_config_flags_t flags) {
platform_bus_t* bus = dev->bus;
if (!bus->gpio.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->gpio_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->gpios[index].gpio;
return gpio_config(&bus->gpio, index, flags);
}
static zx_status_t platform_dev_gpio_read(platform_dev_t* dev, uint32_t index, uint8_t* out_value) {
platform_bus_t* bus = dev->bus;
if (!bus->gpio.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->gpio_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->gpios[index].gpio;
return gpio_read(&bus->gpio, index, out_value);
}
static zx_status_t platform_dev_gpio_write(platform_dev_t* dev, uint32_t index, uint8_t value) {
platform_bus_t* bus = dev->bus;
if (!bus->gpio.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->gpio_count) {
return ZX_ERR_INVALID_ARGS;
}
index = dev->gpios[index].gpio;
return gpio_write(&bus->gpio, index, value);
}
static zx_status_t platform_i2c_get_channel(platform_dev_t* dev, uint32_t index,
pdev_i2c_resp_t* resp) {
platform_bus_t* bus = dev->bus;
if (!bus->i2c.ops) {
return ZX_ERR_NOT_SUPPORTED;
}
if (index >= dev->i2c_channel_count) {
return ZX_ERR_INVALID_ARGS;
}
pbus_i2c_channel_t* pdev_channel = &dev->i2c_channels[index];
i2c_channel_t* channel = calloc(1, sizeof(i2c_channel_t));
if (!channel) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = i2c_get_channel_by_address(&bus->i2c, pdev_channel->bus_id,
pdev_channel->address, channel);
if (status == ZX_OK) {
resp->server_ctx = channel;
} else {
free(channel);
return status;
}
status = i2c_get_max_transfer_size(channel, &resp->max_transfer_size);
if (status != ZX_OK) {
i2c_channel_release(channel);
free(channel);
return status;
}
return ZX_OK;
}
static void platform_i2c_complete(zx_status_t status, const uint8_t* data, size_t actual,
void* cookie) {
i2c_txn_t* txn = cookie;
if (actual > PDEV_I2C_MAX_TRANSFER_SIZE) {
status = ZX_ERR_BUFFER_TOO_SMALL;
}
struct {
pdev_resp_t resp;
uint8_t data[PDEV_I2C_MAX_TRANSFER_SIZE];
} resp = {
.resp = {
.txid = txn->txid,
.status = status,
.i2c = {
.txn_ctx = {
.complete_cb = txn->complete_cb,
.cookie = txn->cookie,
},
},
},
};
if (status == ZX_OK) {
memcpy(resp.data, data, actual);
}
status = zx_channel_write(txn->channel, 0, &resp, sizeof(resp.resp) + actual, NULL, 0);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_i2c_read_complete: zx_channel_write failed %d\n", status);
}
put_i2c_txn(txn);
}
static zx_status_t platform_i2c_transact(platform_dev_t* dev, pdev_req_t* req, uint8_t* data,
zx_handle_t channel) {
// TODO(voydanoff) Do not rely on client passing back a pointer to us.
// We need a safer solution for this.
i2c_channel_t* i2c_channel = (i2c_channel_t *)req->i2c.server_ctx;
i2c_txn_t* txn = get_i2c_txn(dev->bus, req, channel);
if (!txn) {
return ZX_ERR_NO_MEMORY;
}
return i2c_transact(i2c_channel, data, req->i2c.txn_ctx.write_length,
req->i2c.txn_ctx.read_length, platform_i2c_complete, txn);
}
static zx_status_t platform_i2c_get_bitrate(platform_dev_t* dev, pdev_i2c_req_t* req,
pdev_i2c_resp_t* resp) {
i2c_channel_t* channel = (i2c_channel_t *)req->server_ctx;
return i2c_set_bitrate(channel, req->bitrate);
}
static void platform_i2c_channel_release(platform_dev_t* dev, pdev_i2c_req_t* req) {
i2c_channel_t* channel = (i2c_channel_t *)req->server_ctx;
return i2c_channel_release(channel);
}
static zx_status_t platform_dev_rxrpc(void* ctx, zx_handle_t channel) {
platform_dev_t* dev = ctx;
struct {
pdev_req_t req;
uint8_t data[PDEV_I2C_MAX_TRANSFER_SIZE];
} req_data;
pdev_req_t* req = &req_data.req;
pdev_resp_t resp;
uint32_t len = sizeof(req_data);
zx_status_t status = zx_channel_read(channel, 0, &req_data, NULL, len, 0, &len, NULL);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_rxrpc: zx_channel_read failed %d\n", status);
return status;
}
resp.txid = req->txid;
zx_handle_t handle = ZX_HANDLE_INVALID;
uint32_t handle_count = 0;
switch (req->op) {
case PDEV_GET_MMIO:
resp.status = platform_dev_get_mmio(dev, req->index, &handle, &handle_count);
break;
case PDEV_GET_INTERRUPT:
resp.status = platform_dev_get_interrupt(dev, req->index, &handle, &handle_count);
break;
case PDEV_UMS_GET_INITIAL_MODE:
resp.status = platform_dev_ums_get_initial_mode(dev, &resp.usb_mode);
break;
case PDEV_UMS_SET_MODE:
resp.status = platform_dev_ums_set_mode(dev, req->usb_mode);
break;
case PDEV_GPIO_CONFIG:
resp.status = platform_dev_gpio_config(dev, req->index, req->gpio_flags);
break;
case PDEV_GPIO_READ:
resp.status = platform_dev_gpio_read(dev, req->index, &resp.gpio_value);
break;
case PDEV_GPIO_WRITE:
resp.status = platform_dev_gpio_write(dev, req->index, req->gpio_value);
break;
case PDEV_I2C_GET_CHANNEL:
resp.status = platform_i2c_get_channel(dev, req->index, &resp.i2c);
break;
case PDEV_I2C_TRANSACT:
resp.status = platform_i2c_transact(dev, req, req_data.data, channel);
if (resp.status == ZX_OK) {
// If platform_i2c_transact succeeds, we return immmediately instead of calling
// zx_channel_write below. Instead we will respond in platform_i2c_complete().
return ZX_OK;
}
break;
case PDEV_I2C_SET_BITRATE:
resp.status = platform_i2c_get_bitrate(dev, &req->i2c, &resp.i2c);
break;
case PDEV_I2C_CHANNEL_RELEASE:
platform_i2c_channel_release(dev, &req->i2c);
break;
default:
zxlogf(ERROR, "platform_dev_rxrpc: unknown op %u\n", req->op);
return ZX_ERR_INTERNAL;
}
// set op to match request so zx_channel_write will return our response
status = zx_channel_write(channel, 0, &resp, sizeof(resp), (handle_count == 1 ? &handle : NULL),
handle_count);
if (status != ZX_OK) {
zxlogf(ERROR, "platform_dev_rxrpc: zx_channel_write failed %d\n", status);
}
return status;
}
void platform_dev_free(platform_dev_t* dev) {
free(dev->mmios);
free(dev->irqs);
free(dev->gpios);
free(dev->i2c_channels);
free(dev);
}
static zx_protocol_device_t platform_dev_proto = {
.version = DEVICE_OPS_VERSION,
.rxrpc = platform_dev_rxrpc,
// Note that we do not have a release callback here because we
// need to support re-adding platform devices when they are reenabled.
};
zx_status_t platform_device_add(platform_bus_t* bus, const pbus_dev_t* pdev, uint32_t flags) {
zx_status_t status = ZX_OK;
if (flags & ~PDEV_ADD_DISABLED) {
return ZX_ERR_INVALID_ARGS;
}
platform_dev_t* dev = calloc(1, sizeof(platform_dev_t));
if (!dev) {
return ZX_ERR_NO_MEMORY;
}
if (pdev->mmio_count) {
size_t size = pdev->mmio_count * sizeof(*pdev->mmios);
dev->mmios = malloc(size);
if (!dev->mmios) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->mmios, pdev->mmios, size);
dev->mmio_count = pdev->mmio_count;
}
if (pdev->irq_count) {
size_t size = pdev->irq_count * sizeof(*pdev->irqs);
dev->irqs = malloc(size);
if (!dev->irqs) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->irqs, pdev->irqs, size);
dev->irq_count = pdev->irq_count;
}
if (pdev->gpio_count) {
size_t size = pdev->gpio_count * sizeof(*pdev->gpios);
dev->gpios = malloc(size);
if (!dev->gpios) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->gpios, pdev->gpios, size);
dev->gpio_count = pdev->gpio_count;
}
if (pdev->i2c_channel_count) {
size_t size = pdev->i2c_channel_count * sizeof(*pdev->i2c_channels);
dev->i2c_channels = malloc(size);
if (!dev->i2c_channels) {
status = ZX_ERR_NO_MEMORY;
goto fail;
}
memcpy(dev->i2c_channels, pdev->i2c_channels, size);
dev->i2c_channel_count = pdev->i2c_channel_count;
}
dev->bus = bus;
strlcpy(dev->name, pdev->name, sizeof(dev->name));
dev->vid = pdev->vid;
dev->pid = pdev->pid;
dev->did = pdev->did;
list_add_tail(&bus->devices, &dev->node);
if ((flags & PDEV_ADD_DISABLED) == 0) {
status = platform_device_enable(dev, true);
}
fail:
if (status != ZX_OK) {
platform_dev_free(dev);
}
return status;
}
zx_status_t platform_device_enable(platform_dev_t* dev, bool enable) {
zx_status_t status = ZX_OK;
if (enable && !dev->enabled) {
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, dev->vid},
{BIND_PLATFORM_DEV_PID, 0, dev->pid},
{BIND_PLATFORM_DEV_DID, 0, dev->did},
};
char namestr[ZX_DEVICE_NAME_MAX];
snprintf(namestr, sizeof(namestr), "%04x:%04x:%04x", dev->vid, dev->pid, dev->did);
char argstr[64];
snprintf(argstr, sizeof(argstr), "pdev:%s,", namestr);
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = namestr,
.ctx = dev,
.ops = &platform_dev_proto,
.proto_id = ZX_PROTOCOL_PLATFORM_DEV,
.props = props,
.prop_count = countof(props),
.proxy_args = argstr,
.flags = DEVICE_ADD_MUST_ISOLATE,
};
// add PCI root at top level
zx_device_t* parent = dev->bus->zxdev;
if (dev->did == PDEV_DID_KPCI) {
parent = device_get_parent(parent);
}
status = device_add(parent, &args, &dev->zxdev);
} else if (!enable && dev->enabled) {
device_remove(dev->zxdev);
dev->zxdev = NULL;
}
if (status == ZX_OK) {
dev->enabled = enable;
}
return status;
}