blob: 79fb441b75d69ed41fb288ebee26eaa1b45cbce9 [file] [log] [blame]
// Copyright 2018 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 <zircon/syscalls/port.h>
#include <soc/imx8m/imx8m.h>
#include <soc/imx8m/imx8m-hw.h>
#include <soc/imx8m/imx8m-iomux.h>
#include <soc/imx8m/imx8m-gpio.h>
#include <hw/reg.h>
#include <zircon/assert.h>
#include <zircon/types.h>
typedef struct {
platform_device_protocol_t pdev;
platform_bus_protocol_t pbus;
gpio_protocol_t gpio;
zx_device_t* zxdev;
io_buffer_t mmios[IMX_GPIO_BLOCKS];
io_buffer_t mmio_iomux;
mtx_t lock[IMX_GPIO_BLOCKS];
zx_handle_t inth[IMX_GPIO_INTERRUPTS];
zx_handle_t vinth[IMX_GPIO_MAX];
zx_handle_t porth;
thrd_t irq_handler;
mtx_t gpio_lock;
} imx8_gpio_t;
#define READ32_GPIO_REG(block_index, offset) \
readl(io_buffer_virt(&gpio->mmios[block_index]) + offset)
#define WRITE32_GPIO_REG(block_index, offset, value) writel(value, \
io_buffer_virt(&gpio->mmios[block_index]) + offset)
static zx_status_t imx8_gpio_config(void* ctx, uint32_t pin, uint32_t flags) {
uint32_t gpio_block;
uint32_t gpio_pin;
uint32_t regVal;
imx8_gpio_t* gpio = ctx;
gpio_block = IMX_NUM_TO_BLOCK(pin);
gpio_pin = IMX_NUM_TO_BIT(pin);
if (gpio_block >= IMX_GPIO_BLOCKS || gpio_pin >= 32) {
zxlogf(ERROR, "%s: Invalid GPIO pin (pin = %d Block = %d, Offset = %d)\n",
__FUNCTION__, pin, gpio_block, gpio_pin);
return ZX_ERR_INVALID_ARGS;
}
mtx_lock(&gpio->lock[gpio_block]);
regVal = READ32_GPIO_REG(gpio_block, IMX_GPIO_GDIR);
regVal &= ~(1 << gpio_pin);
uint32_t direction = flags & GPIO_DIR_MASK;
if (direction & GPIO_DIR_OUT) {
regVal |= (GPIO_OUTPUT << gpio_pin);
} else {
regVal |= (GPIO_INPUT << gpio_pin);
}
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_GDIR, regVal);
mtx_unlock(&gpio->lock[gpio_block]);
return ZX_OK;
}
static zx_status_t imx8_gpio_read(void* ctx, uint32_t pin, uint8_t* out_value) {
uint32_t gpio_block;
uint32_t gpio_pin;
uint32_t regVal;
imx8_gpio_t* gpio = ctx;
gpio_block = IMX_NUM_TO_BLOCK(pin);
gpio_pin = IMX_NUM_TO_BIT(pin);
if (gpio_block >= IMX_GPIO_BLOCKS || gpio_pin >= 32) {
zxlogf(ERROR, "%s: Invalid GPIO pin (pin = %d Block = %d, Offset = %d)\n",
__FUNCTION__, pin, gpio_block, gpio_pin);
return ZX_ERR_INVALID_ARGS;
}
mtx_lock(&gpio->lock[gpio_block]);
regVal = READ32_GPIO_REG(gpio_block, IMX_GPIO_DR);
regVal >>= (gpio_pin);
regVal &= 1;
*out_value = regVal;
mtx_unlock(&gpio->lock[gpio_block]);
return ZX_OK;
}
static zx_status_t imx8_gpio_write(void* ctx, uint32_t pin, uint8_t value) {
uint32_t gpio_block;
uint32_t gpio_pin;
uint32_t regVal;
imx8_gpio_t* gpio = ctx;
gpio_block = IMX_NUM_TO_BLOCK(pin);
gpio_pin = IMX_NUM_TO_BIT(pin);
if (gpio_block >= IMX_GPIO_BLOCKS || gpio_pin >= 32) {
zxlogf(ERROR, "%s: Invalid GPIO pin (pin = %d Block = %d, Offset = %d)\n",
__FUNCTION__, pin, gpio_block, gpio_pin);
return ZX_ERR_INVALID_ARGS;
}
mtx_lock(&gpio->lock[gpio_block]);
regVal = READ32_GPIO_REG(gpio_block, IMX_GPIO_DR);
regVal &= ~(1 << gpio_pin);
regVal |= (value << gpio_pin);
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_DR, regVal);
mtx_unlock(&gpio->lock[gpio_block]);
return ZX_OK;
}
// Configure a pin for an alternate function specified by fn
static zx_status_t imx8_gpio_set_alt_function(void* ctx, const uint32_t pin, const uint64_t fn) {
imx8_gpio_t* gpio = ctx;
iomux_cfg_struct s_cfg = (iomux_cfg_struct) fn;
volatile uint8_t* iomux = (volatile uint8_t*)io_buffer_virt(&gpio->mmio_iomux);
zxlogf(SPEW, "0x%lx\n", s_cfg);
zxlogf(SPEW, "val = 0x%lx, reg = %p\n",
IOMUX_CFG_MUX_MODE_VAL(GET_MUX_MODE_VAL(s_cfg)) |
IOMUX_CFG_SION_VAL(GET_SION_VAL(s_cfg)),
iomux + GET_MUX_CTL_OFF_VAL(s_cfg));
zxlogf(SPEW, "val = 0x%lx, reg = %p\n",
IOMUX_CFG_DSE_VAL(GET_DSE_VAL(s_cfg)) |
IOMUX_CFG_SRE_VAL(GET_SRE_VAL(s_cfg)) |
IOMUX_CFG_ODE_VAL(GET_ODE_VAL(s_cfg)) |
IOMUX_CFG_PUE_VAL(GET_PUE_VAL(s_cfg)) |
IOMUX_CFG_HYS_VAL(GET_HYS_VAL(s_cfg)) |
IOMUX_CFG_LVTTL_VAL(GET_LVTTL_VAL(s_cfg)) |
IOMUX_CFG_VSEL_VAL(GET_VSEL_VAL(s_cfg)),
iomux + GET_PAD_CTL_OFF_VAL(s_cfg));
zxlogf(SPEW, "val = 0x%lx, reg = %p\n",
IOMUX_CFG_DAISY_VAL(GET_DAISY_VAL(s_cfg)),
iomux + GET_SEL_INP_OFF_VAL(s_cfg));
if (GET_MUX_CTL_OFF_VAL(s_cfg)) {
writel(
IOMUX_CFG_MUX_MODE_VAL(GET_MUX_MODE_VAL(s_cfg)) |
IOMUX_CFG_SION_VAL(GET_SION_VAL(s_cfg)),
iomux + GET_MUX_CTL_OFF_VAL(s_cfg));
}
if (GET_PAD_CTL_OFF_VAL(s_cfg)) {
writel(
IOMUX_CFG_DSE_VAL(GET_DSE_VAL(s_cfg)) |
IOMUX_CFG_SRE_VAL(GET_SRE_VAL(s_cfg)) |
IOMUX_CFG_ODE_VAL(GET_ODE_VAL(s_cfg)) |
IOMUX_CFG_PUE_VAL(GET_PUE_VAL(s_cfg)) |
IOMUX_CFG_HYS_VAL(GET_HYS_VAL(s_cfg)) |
IOMUX_CFG_LVTTL_VAL(GET_LVTTL_VAL(s_cfg)) |
IOMUX_CFG_VSEL_VAL(GET_VSEL_VAL(s_cfg)),
iomux + GET_PAD_CTL_OFF_VAL(s_cfg));
}
if (GET_SEL_INP_OFF_VAL(s_cfg)) {
writel(IOMUX_CFG_DAISY_VAL(GET_DAISY_VAL(s_cfg)),
iomux + GET_SEL_INP_OFF_VAL(s_cfg));
}
return ZX_OK;
}
static void imx8_gpio_mask_irq(imx8_gpio_t *gpio, uint32_t gpio_block, uint32_t gpio_pin) {
uint32_t regVal = READ32_GPIO_REG(gpio_block, IMX_GPIO_IMR);
regVal &= ~(1 << gpio_pin);
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_IMR, regVal);
}
static void imx8_gpio_unmask_irq(imx8_gpio_t *gpio, uint32_t gpio_block, uint32_t gpio_pin) {
uint32_t regVal = READ32_GPIO_REG(gpio_block, IMX_GPIO_IMR);
regVal |= (1 << gpio_pin);
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_IMR, regVal);
}
static int imx8_gpio_irq_handler(void *arg) {
imx8_gpio_t* gpio = arg;
zx_port_packet_t packet;
zx_status_t status = ZX_OK;
uint32_t gpio_block;
uint32_t isr;
uint32_t imr;
uint32_t pin;
while(1) {
status = zx_port_wait(gpio->porth, ZX_TIME_INFINITE, &packet);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_port_wait failed %d \n", __FUNCTION__, status);
goto fail;
}
zxlogf(INFO, "GPIO Interrupt %x triggered\n", (unsigned int)packet.key);
status = zx_interrupt_ack(gpio->inth[packet.key]);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_interrupt_ack failed %d \n", __FUNCTION__, status);
goto fail;
}
gpio_block = IMX_INT_NUM_TO_BLOCK(packet.key);
isr = READ32_GPIO_REG(gpio_block, IMX_GPIO_ISR);
imr = READ32_GPIO_REG(gpio_block, IMX_GPIO_IMR);
// Get the status of the enabled interrupts
// Get the last valid interrupt pin
uint32_t valid_irqs = (isr & imr);
if (valid_irqs) {
pin = __builtin_ctz(valid_irqs);
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_ISR, 1 << pin);
pin = gpio_block*IMX_GPIO_PER_BLOCK + pin;
if (gpio->vinth[pin] != ZX_HANDLE_INVALID) {
// Trigger the corresponding virtual interrupt
status = zx_interrupt_trigger(gpio->vinth[pin], 0, zx_clock_get_monotonic());
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_interrupt_trigger failed %d \n", __FUNCTION__, status);
goto fail;
}
}
}
}
fail:
for (int i=0; i<IMX_GPIO_INTERRUPTS; i++) {
zx_interrupt_destroy(gpio->inth[i]);
zx_handle_close(gpio->inth[i]);
}
return status;
}
static zx_status_t imx8_gpio_get_interrupt(void *ctx, uint32_t pin,
uint32_t flags,
zx_handle_t *out_handle) {
uint32_t gpio_block;
uint32_t gpio_pin;
imx8_gpio_t* gpio = ctx;
uint32_t regVal;
uint32_t interrupt_type;
zx_status_t status = ZX_OK;
uint32_t icr_offset;
gpio_block = IMX_NUM_TO_BLOCK(pin);
gpio_pin = IMX_NUM_TO_BIT(pin);
if (gpio_block >= IMX_GPIO_BLOCKS || gpio_pin >= IMX_GPIO_PER_BLOCK) {
zxlogf(ERROR, "%s: Invalid GPIO pin (pin = %d Block = %d, Offset = %d)\n",
__FUNCTION__, pin, gpio_block, gpio_pin);
return ZX_ERR_INVALID_ARGS;
}
// Create Virtual Interrupt
status = zx_interrupt_create(0, 0, ZX_INTERRUPT_VIRTUAL, &gpio->vinth[pin]);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_irq_create failed %d \n", __FUNCTION__, status);
return status;
}
// Store the Virtual Interrupt
status = zx_handle_duplicate(gpio->vinth[pin], ZX_RIGHT_SAME_RIGHTS, out_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_handle_duplicate failed %d \n", __FUNCTION__, status);
return status;
}
mtx_lock(&gpio->lock[gpio_block]);
// Select EGDE or LEVEL and polarity
switch (flags & ZX_INTERRUPT_MODE_MASK) {
case ZX_INTERRUPT_MODE_EDGE_LOW:
interrupt_type = IMX_GPIO_FALLING_EDGE_INTERRUPT;
break;
case ZX_INTERRUPT_MODE_EDGE_HIGH:
interrupt_type = IMX_GPIO_RISING_EDGE_INTERRUPT;
break;
case ZX_INTERRUPT_MODE_LEVEL_LOW:
interrupt_type = IMX_GPIO_LOW_LEVEL_INTERRUPT;
break;
case ZX_INTERRUPT_MODE_LEVEL_HIGH:
interrupt_type = IMX_GPIO_HIGH_LEVEL_INTERRUPT;
break;
case ZX_INTERRUPT_MODE_EDGE_BOTH:
interrupt_type = IMX_GPIO_BOTH_EDGE_INTERRUPT;
break;
default:
status = ZX_ERR_INVALID_ARGS;
goto fail;
}
if (interrupt_type == IMX_GPIO_BOTH_EDGE_INTERRUPT) {
regVal = READ32_GPIO_REG(gpio_block, IMX_GPIO_EDGE_SEL);
regVal |= (1 << gpio_pin);
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_EDGE_SEL, regVal);
} else {
// Select which ICR register to program
if (gpio_pin >= IMX_GPIO_MAX_ICR_PIN) {
icr_offset = IMX_GPIO_ICR2;
} else {
icr_offset = IMX_GPIO_ICR1;
}
regVal = READ32_GPIO_REG(gpio_block, icr_offset);
regVal &= ~(IMX_GPIO_ICR_MASK << IMX_GPIO_ICR_SHIFT(gpio_pin));
regVal |= (interrupt_type << IMX_GPIO_ICR_SHIFT(gpio_pin));
WRITE32_GPIO_REG(gpio_block, icr_offset, regVal);
}
// Mask the Interrupt
imx8_gpio_mask_irq(gpio, gpio_block, gpio_pin);
// Clear the Interrupt Status
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_ISR, 1 << gpio_pin);
// Unmask the Interrupt
imx8_gpio_unmask_irq(gpio, gpio_block, gpio_pin);
fail:
mtx_unlock(&gpio->lock[gpio_block]);
return status;
}
static zx_status_t imx8_gpio_release_interrupt(void *ctx, uint32_t pin) {
imx8_gpio_t* gpio = ctx;
zx_status_t status = ZX_OK;
uint32_t gpio_pin = IMX_NUM_TO_BIT(pin);
uint32_t gpio_block = IMX_NUM_TO_BLOCK(pin);
if (gpio_block >= IMX_GPIO_BLOCKS || gpio_pin >= IMX_GPIO_PER_BLOCK) {
zxlogf(ERROR, "%s: Invalid GPIO pin (pin = %d Block = %d, Offset = %d)\n",
__FUNCTION__, pin, gpio_block, gpio_pin);
return ZX_ERR_INVALID_ARGS;
}
mtx_lock(&gpio->gpio_lock);
// Mask the interrupt
imx8_gpio_mask_irq(gpio, gpio_block, gpio_pin);
zx_handle_close(gpio->vinth[pin]);
gpio->vinth[pin] = ZX_HANDLE_INVALID;
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_handle_close failed %d \n", __FUNCTION__, status);
goto fail;
}
fail:
mtx_unlock(&gpio->gpio_lock);
return status;
}
static gpio_protocol_ops_t gpio_ops = {
.config = imx8_gpio_config,
.set_alt_function = imx8_gpio_set_alt_function,
.read = imx8_gpio_read,
.write = imx8_gpio_write,
.get_interrupt = imx8_gpio_get_interrupt,
.release_interrupt = imx8_gpio_release_interrupt,
};
static void imx8_gpio_release(void* ctx)
{
unsigned i;
imx8_gpio_t* gpio = ctx;
mtx_lock(&gpio->gpio_lock);
for (i = 0; i < IMX_GPIO_BLOCKS; i++) {
io_buffer_release(&gpio->mmios[i]);
}
io_buffer_release(&gpio->mmio_iomux);
for (int i=0; i<IMX_GPIO_INTERRUPTS; i++) {
zx_interrupt_destroy(gpio->inth[i]);
zx_handle_close(gpio->inth[i]);
}
free(gpio);
mtx_unlock(&gpio->gpio_lock);
}
static zx_protocol_device_t gpio_device_proto = {
.version = DEVICE_OPS_VERSION,
.release = imx8_gpio_release,
};
static zx_status_t imx8_gpio_bind(void* ctx, zx_device_t* parent)
{
zx_status_t status;
unsigned i;
imx8_gpio_t* gpio = calloc(1, sizeof(imx8_gpio_t));
if (!gpio) {
return ZX_ERR_NO_MEMORY;
}
status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &gpio->pdev);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_PLATFORM_DEV not available %d \n", __FUNCTION__, status);
goto fail;
}
status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_BUS, &gpio->pbus);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_PLATFORM_BUS not available %d\n", __FUNCTION__, status);
goto fail;
}
for (i = 0; i < IMX_GPIO_BLOCKS; i++) {
status = pdev_map_mmio_buffer(&gpio->pdev, i, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&gpio->mmios[i]);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_map_mmio_buffer gpio failed %d\n", __FUNCTION__, status);
goto fail;
}
mtx_init(&gpio->lock[i], mtx_plain);
}
status = pdev_map_mmio_buffer(&gpio->pdev, IMX_GPIO_BLOCKS, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&gpio->mmio_iomux);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_map_mmio_buffer iomux failed %d\n", __FUNCTION__, status);
goto fail;
}
pdev_device_info_t info;
status = pdev_get_device_info(&gpio->pdev, &info);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_get_device_info failed %d\n", __FUNCTION__, status);
goto fail;
}
status = zx_port_create(1/*PORT_BIND_TO_INTERRUPT*/, &gpio->porth);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_port_create failed %d\n", __FUNCTION__, status);
goto fail;
}
for (i=0; i<info.irq_count; i++) {
// Create Interrupt Object
status = pdev_map_interrupt(&gpio->pdev, i,
&gpio->inth[i]);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_map_interrupt failed %d\n", __FUNCTION__, status);
goto fail;
}
// The KEY is the Interrupt Number for our usecase
status = zx_interrupt_bind(gpio->inth[i], gpio->porth, i, 0/*optons*/);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: zx_interrupt_bind failed %d\n", __FUNCTION__, status);
goto fail;
}
}
thrd_create_with_name(&gpio->irq_handler, imx8_gpio_irq_handler, gpio, "imx8_gpio_irq_handler");
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "imx8-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, "%s: device_add failed! %d\n", __FUNCTION__, status);
goto fail;
}
gpio->gpio.ops = &gpio_ops;
gpio->gpio.ctx = gpio;
pbus_register_protocol(&gpio->pbus, ZX_PROTOCOL_GPIO, &gpio->gpio, NULL);
return ZX_OK;
fail:
imx8_gpio_release(gpio);
return status;
}
static zx_driver_ops_t imx8_gpio_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = imx8_gpio_bind,
};
ZIRCON_DRIVER_BEGIN(imx8_gpio, imx8_gpio_driver_ops, "zircon", "0.1", 6)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_NXP),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_DID, PDEV_DID_IMX_GPIO),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_IMX8MEVK),
ZIRCON_DRIVER_END(imx8_gpio)