blob: d3021225c78607085660b6b519d9fc7068499359 [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 <threads.h>
#include <bits/limits.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/protocol/gpio.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 ALT_FUNCTION_MAX 15
typedef struct {
uint32_t start_pin;
uint32_t pin_block;
uint32_t pin_count;
uint32_t mux_offset;
uint32_t oen_offset;
uint32_t input_offset;
uint32_t output_offset;
uint32_t output_shift; // Used for GPIOAO block
uint32_t mmio_index;
mtx_t lock;
} aml_gpio_block_t;
typedef struct {
platform_device_protocol_t pdev;
gpio_protocol_t gpio;
zx_device_t* zxdev;
pdev_vmo_buffer_t mmios[2]; // separate MMIO for AO domain
aml_gpio_block_t* gpio_blocks;
size_t block_count;
} aml_gpio_t;
#include "a113-blocks.h"
#include "s905d2-blocks.h"
static zx_status_t aml_pin_to_block(aml_gpio_t* gpio, const uint32_t pinid, aml_gpio_block_t** result) {
ZX_DEBUG_ASSERT(result);
for (size_t i = 0; i < gpio->block_count; i++) {
aml_gpio_block_t* gpio_block = &gpio->gpio_blocks[i];
const uint32_t end_pin = gpio_block->start_pin + gpio_block->pin_count;
if (pinid >= gpio_block->start_pin && pinid < end_pin) {
*result = gpio_block;
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
static zx_status_t aml_gpio_config(void* ctx, uint32_t index, uint32_t flags) {
aml_gpio_t* gpio = ctx;
zx_status_t status;
aml_gpio_block_t* block;
if ((status = aml_pin_to_block(gpio, index, &block)) != ZX_OK) {
zxlogf(ERROR, "aml_gpio_config: pin not found %u\n", index);
return status;
}
const uint32_t pinid = index - block->pin_block;
mtx_lock(&block->lock);
volatile uint32_t* reg = (volatile uint32_t*)gpio->mmios[block->mmio_index].vaddr;
reg += block->oen_offset;
uint32_t regval = readl(reg);
const uint32_t pinmask = 1 << pinid;
if (flags & GPIO_DIR_OUT) {
regval &= ~pinmask;
} else {
regval |= pinmask;
}
writel(regval, reg);
mtx_unlock(&block->lock);
return ZX_OK;
}
// Configure a pin for an alternate function specified by fn
static zx_status_t aml_gpio_set_alt_function(void* ctx, const uint32_t pin, const uint32_t fn) {
aml_gpio_t* gpio = ctx;
if (fn > ALT_FUNCTION_MAX) {
zxlogf(ERROR, "aml_config_pinmux: pin mux alt config out of range"
" %u\n", fn);
return ZX_ERR_OUT_OF_RANGE;
}
zx_status_t status;
aml_gpio_block_t* block;
if (((status = aml_pin_to_block(gpio, pin, &block)) != ZX_OK) != ZX_OK) {
zxlogf(ERROR, "aml_config_pinmux: pin not found %u\n", pin);
return status;
}
// Points to the control register.
volatile uint32_t* reg = (volatile uint32_t*)gpio->mmios[block->mmio_index].vaddr;
reg += block->mux_offset;
// Sanity Check: pin_to_block must return a block that contains `pin`
// therefore `pin` must be greater than or equal to the first
// pin of the block.
ZX_DEBUG_ASSERT(pin >= block->start_pin);
// Each Pin Mux is controlled by a 4 bit wide field in `reg`
// Compute the offset for this pin.
const uint32_t pin_shift = (pin - block->start_pin) * 4;
const uint32_t mux_mask = ~(0x0F << pin_shift);
const uint32_t fn_val = fn << pin_shift;
mtx_lock(&block->lock);
uint32_t regval = readl(reg);
regval &= mux_mask; // Remove the previous value for the mux
regval |= fn_val; // Assign the new value to the mux
writel(regval, reg);
mtx_unlock(&block->lock);
return ZX_OK;
}
static zx_status_t aml_gpio_read(void* ctx, uint32_t index, uint8_t* out_value) {
aml_gpio_t* gpio = ctx;
zx_status_t status;
aml_gpio_block_t* block;
if ((status = aml_pin_to_block(gpio, index, &block)) != ZX_OK) {
zxlogf(ERROR, "aml_config_pinmux: pin not found %u\n", index);
return status;
}
const uint32_t pinindex = index - block->pin_block;
const uint32_t readmask = 1 << pinindex;
volatile uint32_t* reg = (volatile uint32_t*)gpio->mmios[block->mmio_index].vaddr;
reg += block->input_offset;
mtx_lock(&block->lock);
const uint32_t regval = readl(reg);
mtx_unlock(&block->lock);
if (regval & readmask) {
*out_value = 1;
} else {
*out_value = 0;
}
return ZX_OK;
}
static zx_status_t aml_gpio_write(void* ctx, uint32_t index, uint8_t value) {
aml_gpio_t* gpio = ctx;
zx_status_t status;
aml_gpio_block_t* block;
if ((status = aml_pin_to_block(gpio, index, &block)) != ZX_OK) {
zxlogf(ERROR, "aml_gpio_write: pin not found %u\n", index);
return status;
}
uint32_t pinindex = index - block->pin_block;
volatile uint32_t* reg = (volatile uint32_t*)gpio->mmios[block->mmio_index].vaddr;
reg += block->output_offset;
pinindex += block->output_shift;
printf("aml_gpio_write index %u value %d offset %u pinindex %u\n", index, value, block->output_offset, pinindex);
mtx_lock(&block->lock);
uint32_t regval = readl(reg);
if (value) {
regval |= 1 << pinindex;
} else {
regval &= ~(1 << pinindex);
}
writel(regval, reg);
mtx_unlock(&block->lock);
return ZX_OK;
}
static gpio_protocol_ops_t gpio_ops = {
.config = aml_gpio_config,
.set_alt_function = aml_gpio_set_alt_function,
.read = aml_gpio_read,
.write = aml_gpio_write,
};
static void aml_gpio_release(void* ctx) {
aml_gpio_t* gpio = ctx;
for (unsigned i = 0; i < countof(gpio->mmios); i++) {
pdev_vmo_buffer_release(&gpio->mmios[i]);
}
free(gpio);
}
static zx_protocol_device_t gpio_device_proto = {
.version = DEVICE_OPS_VERSION,
.release = aml_gpio_release,
};
static zx_status_t aml_gpio_bind(void* ctx, zx_device_t* parent) {
zx_status_t status;
aml_gpio_t* gpio = calloc(1, sizeof(aml_gpio_t));
if (!gpio) {
return ZX_ERR_NO_MEMORY;
}
if ((status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &gpio->pdev)) != ZX_OK) {
zxlogf(ERROR, "aml_gpio_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_gpio_bind: ZX_PROTOCOL_PLATFORM_BUS not available\n");
goto fail;
}
for (unsigned i = 0; i < countof(gpio->mmios); i++) {
status = pdev_map_mmio_buffer(&gpio->pdev, i, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&gpio->mmios[i]);
if (status != ZX_OK) {
zxlogf(ERROR, "aml_gpio_bind: pdev_map_mmio_buffer failed\n");
goto fail;
}
}
pdev_device_info_t info;
status = pdev_get_device_info(&gpio->pdev, &info);
if (status != ZX_OK) {
zxlogf(ERROR, "aml_gpio_bind: pdev_get_device_info failed\n");
goto fail;
}
switch (info.pid) {
case PDEV_PID_AMLOGIC_A113:
gpio->gpio_blocks = a113_gpio_blocks;
gpio->block_count = countof(a113_gpio_blocks);
break;
case PDEV_PID_AMLOGIC_S905D2:
gpio->gpio_blocks = s905d2_gpio_blocks;
gpio->block_count = countof(s905d2_gpio_blocks);
break;
default:
zxlogf(ERROR, "aml_gpio_bind: unsupported SOC PID %u\n", info.pid);
goto fail;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "aml-a113-gpio",
.ctx = gpio,
.ops = &gpio_device_proto,
.flags = DEVICE_ADD_NON_BINDABLE,
};
status = device_add(parent, &args, &gpio->zxdev);
if (status != ZX_OK) {
zxlogf(ERROR, "aml_gpio_bind: device_add failed\n");
goto fail;
}
gpio->gpio.ops = &gpio_ops;
gpio->gpio.ctx = gpio;
pbus_set_protocol(&pbus, ZX_PROTOCOL_GPIO, &gpio->gpio);
return ZX_OK;
fail:
aml_gpio_release(gpio);
return status;
}
static zx_driver_ops_t aml_gpio_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = aml_gpio_bind,
};
ZIRCON_DRIVER_BEGIN(aml_gpio, aml_gpio_driver_ops, "zircon", "0.1", 5)
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_DID, PDEV_DID_AMLOGIC_GPIO),
// we support multiple SOC variants
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_A113),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S905D2),
ZIRCON_DRIVER_END(aml_gpio)