blob: aeed7e95947cb1513ad4da79bfd8ed0b983bc64b [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 "imx8-gpio.h"
#include <bits/limits.h>
#include <ddk/debug.h>
#include <hw/reg.h>
#include <soc/imx8m/imx8m-iomux.h>
#include <zircon/assert.h>
#include <zircon/types.h>
#include <zircon/syscalls/port.h>
zx_status_t imx8_gpio_config_in(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);
regVal |= (GPIO_INPUT << gpio_pin);
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_GDIR, regVal);
mtx_unlock(&gpio->lock[gpio_block]);
return ZX_OK;
}
zx_status_t imx8_gpio_config_out(void* ctx, uint32_t pin, uint8_t initial_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]);
// Set value before configuring for output
regVal = READ32_GPIO_REG(gpio_block, IMX_GPIO_DR);
regVal &= ~(1 << gpio_pin);
regVal |= (initial_value << gpio_pin);
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_DR, regVal);
regVal = READ32_GPIO_REG(gpio_block, IMX_GPIO_GDIR);
regVal &= ~(1 << gpio_pin);
regVal |= (GPIO_OUTPUT << gpio_pin);
WRITE32_GPIO_REG(gpio_block, IMX_GPIO_GDIR, regVal);
mtx_unlock(&gpio->lock[gpio_block]);
return ZX_OK;
}
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;
}
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;
}
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);
}
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;
}
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;
}
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;
}
zx_status_t imx8_gpio_set_polarity(void* ctx, uint32_t pin,
uint32_t polarity) {
return ZX_ERR_NOT_SUPPORTED;
}