/* * Copyright (c) 2006-2022, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2022-6-30 GuEe-GUI first version */ #include #include #include #include #include "drv_gpio.h" #ifdef BSP_USING_PIN #define GPIODIR 0x400 #define GPIOIS 0x404 #define GPIOIBE 0x408 #define GPIOIEV 0x40c #define GPIOIE 0x410 #define GPIORIS 0x414 #define GPIOMIS 0x418 #define GPIOIC 0x41c #define BIT(x) (1 << (x)) #define PL061_GPIO_NR 8 static struct pl061 { #ifdef RT_USING_SMP struct rt_spinlock spinlock; #endif void (*(hdr[PL061_GPIO_NR]))(void *args); void *args[PL061_GPIO_NR]; } _pl061; static rt_ubase_t pl061_gpio_base = PL061_GPIO_BASE; rt_inline rt_uint8_t pl061_read8(rt_ubase_t offset) { return HWREG8(pl061_gpio_base + offset); } rt_inline void pl061_write8(rt_ubase_t offset, rt_uint8_t value) { HWREG8(pl061_gpio_base + offset) = value; } static void pl061_pin_mode(struct rt_device *device, rt_base_t pin, rt_uint8_t mode) { int value; rt_uint8_t gpiodir; #ifdef RT_USING_SMP rt_base_t level; #endif if (pin < 0 || pin >= PL061_GPIO_NR) { return; } #ifdef RT_USING_SMP level = rt_spin_lock_irqsave(&_pl061.spinlock); #endif switch (mode) { case PIN_MODE_OUTPUT: value = !!pl061_read8((BIT(pin + 2))); pl061_write8(BIT(pin + 2), 0 << pin); gpiodir = pl061_read8(GPIODIR); gpiodir |= BIT(pin); pl061_write8(GPIODIR, gpiodir); /* * gpio value is set again, because pl061 doesn't allow to set value of * a gpio pin before configuring it in OUT mode. */ pl061_write8((BIT(pin + 2)), value << pin); break; case PIN_MODE_INPUT: gpiodir = pl061_read8(GPIODIR); gpiodir &= ~(BIT(pin)); pl061_write8(GPIODIR, gpiodir); break; } #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&_pl061.spinlock, level); #endif } static void pl061_pin_write(struct rt_device *device, rt_base_t pin, rt_uint8_t value) { pl061_write8(BIT(pin + 2), !!value << pin); } static rt_int8_t pl061_pin_read(struct rt_device *device, rt_base_t pin) { return !!pl061_read8((BIT(pin + 2))); } static rt_err_t pl061_pin_attach_irq(struct rt_device *device, rt_base_t pin, rt_uint8_t mode, void (*hdr)(void *args), void *args) { rt_uint8_t gpiois, gpioibe, gpioiev; rt_uint8_t bit = BIT(mode); #ifdef RT_USING_SMP rt_base_t level; #endif if (pin < 0 || pin >= PL061_GPIO_NR) { return -RT_EINVAL; } #ifdef RT_USING_SMP level = rt_spin_lock_irqsave(&_pl061.spinlock); #endif gpioiev = pl061_read8(GPIOIEV); gpiois = pl061_read8(GPIOIS); gpioibe = pl061_read8(GPIOIBE); if (mode == PIN_IRQ_MODE_HIGH_LEVEL || pin == PIN_IRQ_MODE_LOW_LEVEL) { rt_bool_t polarity = (mode == PIN_IRQ_MODE_HIGH_LEVEL); /* Disable edge detection */ gpioibe &= ~bit; /* Enable level detection */ gpiois |= bit; /* Select polarity */ if (polarity) { gpioiev |= bit; } else { gpioiev &= ~bit; } } else if (mode == PIN_IRQ_MODE_RISING_FALLING) { /* Disable level detection */ gpiois &= ~bit; /* Select both edges, setting this makes GPIOEV be ignored */ gpioibe |= bit; } else if (mode == PIN_IRQ_MODE_RISING || mode == PIN_IRQ_MODE_FALLING) { rt_bool_t rising = (mode == PIN_IRQ_MODE_RISING); /* Disable level detection */ gpiois &= ~bit; /* Clear detection on both edges */ gpioibe &= ~bit; /* Select edge */ if (rising) { gpioiev |= bit; } else { gpioiev &= ~bit; } } else { /* No trigger: disable everything */ gpiois &= ~bit; gpioibe &= ~bit; gpioiev &= ~bit; } pl061_write8(GPIOIS, gpiois); pl061_write8(GPIOIBE, gpioibe); pl061_write8(GPIOIEV, gpioiev); _pl061.hdr[pin] = hdr; _pl061.args[pin] = args; #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&_pl061.spinlock, level); #endif return RT_EOK; } static rt_err_t pl061_pin_detach_irq(struct rt_device *device, rt_base_t pin) { if (pin < 0 || pin >= PL061_GPIO_NR) { return -RT_EINVAL; } _pl061.hdr[pin] = RT_NULL; _pl061.args[pin] = RT_NULL; return RT_EOK; } static rt_err_t pl061_pin_irq_enable(struct rt_device *device, rt_base_t pin, rt_uint8_t enabled) { rt_uint8_t mask = BIT(pin); rt_uint8_t gpioie; #ifdef RT_USING_SMP rt_base_t level; #endif if (pin < 0 || pin >= PL061_GPIO_NR) { return -RT_EINVAL; } #ifdef RT_USING_SMP level = rt_spin_lock_irqsave(&_pl061.spinlock); #endif if (enabled) { gpioie = pl061_read8(GPIOIE) | mask; } else { gpioie = pl061_read8(GPIOIE) & ~mask; } pl061_write8(GPIOIE, gpioie); #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&_pl061.spinlock, level); #endif return RT_EOK; } static const struct rt_pin_ops ops = { pl061_pin_mode, pl061_pin_write, pl061_pin_read, pl061_pin_attach_irq, pl061_pin_detach_irq, pl061_pin_irq_enable, RT_NULL, }; static void rt_hw_gpio_isr(int irqno, void *param) { rt_uint8_t mask; unsigned long pending; #ifdef RT_USING_SMP rt_base_t level; #endif pending = pl061_read8(GPIOMIS); if (pending) { rt_base_t pin; for (pin = 0; pin < PL061_GPIO_NR; ++pin) { if (pending & BIT(pin)) { mask |= BIT(pin); if (_pl061.hdr[pin] != RT_NULL) { _pl061.hdr[pin](_pl061.args[pin]); } } } } #ifdef RT_USING_SMP level = rt_spin_lock_irqsave(&_pl061.spinlock); #endif pl061_write8(GPIOIC, mask); #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&_pl061.spinlock, level); #endif } int rt_hw_gpio_init(void) { #ifdef RT_USING_SMP rt_spin_lock_init(&_pl061.spinlock); #endif #ifdef RT_USING_LWP pl061_gpio_base = (rt_size_t)rt_ioremap((void *)pl061_gpio_base, PL061_GPIO_SIZE); #endif rt_device_pin_register("gpio", &ops, RT_NULL); rt_hw_interrupt_install(PL061_GPIO_IRQNUM, rt_hw_gpio_isr, RT_NULL, "gpio"); rt_hw_interrupt_umask(PL061_GPIO_IRQNUM); return 0; } INIT_DEVICE_EXPORT(rt_hw_gpio_init); #endif /* BSP_USING_PIN */