/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (C) 2019-2020 embedded brains GmbH. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #define IMX_GPIO_ALIAS_NAME "gpioX" /* * i.MX6ULL has 5, i.MX7D has 7 * * Be careful when changing this. The attach() does a simple ASCII conversion. */ #define IMX_MAX_GPIO_MODULES 7 struct imx_gpio_regs { uint32_t dr; uint32_t gdir; uint32_t psr; uint32_t icr1; #define IMX_GPIO_ICR_LOW_LEVEL 0 #define IMX_GPIO_ICR_HIGH_LEVEL 1 #define IMX_GPIO_ICR_RISING_EDGE 2 #define IMX_GPIO_ICR_FALLING_EDGE 3 uint32_t icr2; uint32_t imr; uint32_t isr; uint32_t edge_sel; }; struct imx_gpio { char name[sizeof(IMX_GPIO_ALIAS_NAME)]; struct imx_gpio_regs *regs; rtems_interrupt_lock lock; }; /* The GPIO modules. These will be initialized based on the FDT alias table. */ struct imx_gpio imx_gpio[IMX_MAX_GPIO_MODULES]; const char *imx_gpio_get_name(struct imx_gpio *imx_gpio) { return imx_gpio->name; } static void imx_gpio_attach(void) { size_t i; const void *fdt; fdt = bsp_fdt_get(); memset(imx_gpio, 0, sizeof(imx_gpio)); for (i = 0; i < IMX_MAX_GPIO_MODULES; ++i) { const char *path; int node; const uint32_t *val; uint32_t gpio_regs = 0; int len; memcpy(imx_gpio[i].name, IMX_GPIO_ALIAS_NAME, sizeof(IMX_GPIO_ALIAS_NAME)); imx_gpio[i].name[sizeof(IMX_GPIO_ALIAS_NAME)-2] = (char)('0' + i); path = fdt_get_alias(fdt, imx_gpio[i].name); if (path == NULL) { continue; } node = fdt_path_offset(fdt, path); if (node < 0) { bsp_fatal(IMX_FATAL_GPIO_UNEXPECTED_FDT); } val = fdt_getprop(fdt, node, "reg", &len); if (len > 0) { gpio_regs = fdt32_to_cpu(val[0]); } else { bsp_fatal(IMX_FATAL_GPIO_UNEXPECTED_FDT); } imx_gpio[i].regs = (struct imx_gpio_regs *)gpio_regs; rtems_interrupt_lock_initialize(&imx_gpio[i].lock, imx_gpio[i].name); } } struct imx_gpio *imx_gpio_get_by_index(unsigned idx) { if ((idx < IMX_MAX_GPIO_MODULES) && (imx_gpio[idx].regs != NULL)) { return &imx_gpio[idx]; } return NULL; } struct imx_gpio *imx_gpio_get_by_register(void *regs) { size_t i; for (i = 0; i < IMX_MAX_GPIO_MODULES; ++i) { if (imx_gpio[i].regs == regs) { return &imx_gpio[i]; } } return NULL; } static void imx_gpio_direction_input(struct imx_gpio_pin *pin) { rtems_interrupt_lock_context lock_context; rtems_interrupt_lock_acquire(&pin->gpio->lock, &lock_context); pin->gpio->regs->gdir &= ~pin->mask; rtems_interrupt_lock_release(&pin->gpio->lock, &lock_context); } static void imx_gpio_direction_output(struct imx_gpio_pin *pin) { rtems_interrupt_lock_context lock_context; rtems_interrupt_lock_acquire(&pin->gpio->lock, &lock_context); pin->gpio->regs->gdir |= pin->mask; rtems_interrupt_lock_release(&pin->gpio->lock, &lock_context); } static void imx_gpio_set_interrupt_any_edge(struct imx_gpio_pin *pin) { rtems_interrupt_lock_context lock_context; rtems_interrupt_lock_acquire(&pin->gpio->lock, &lock_context); pin->gpio->regs->edge_sel |= pin->mask; rtems_interrupt_lock_release(&pin->gpio->lock, &lock_context); } static void imx_gpio_set_interrupt_mode(struct imx_gpio_pin *pin, uint32_t mode) { size_t i; for (i=0; i < 32; ++i) { if ((pin->mask & (1u << i)) != 0) { volatile uint32_t *icr; size_t shift; rtems_interrupt_lock_context lock_context; if (i < 16) { icr = &pin->gpio->regs->icr1; shift = 2 * i; } else { icr = &pin->gpio->regs->icr2; shift = 2 * (i - 16); } rtems_interrupt_lock_acquire(&pin->gpio->lock, &lock_context); *icr = (*icr & ~(3u << shift)) | (mode << shift); rtems_interrupt_lock_release(&pin->gpio->lock, &lock_context); } } } rtems_status_code imx_gpio_init_from_fdt_property ( struct imx_gpio_pin *pin, int node_offset, const char *property, enum imx_gpio_mode mode, size_t index ) { int len; const uint32_t *val; rtems_status_code sc = RTEMS_SUCCESSFUL; const void *fdt; uint32_t gpio_regs; const unsigned pin_length_dwords = 3; const unsigned pin_length_bytes = (pin_length_dwords * sizeof(uint32_t)); uint32_t gpio_phandle; uint32_t pin_nr; int cfgnode; memset(pin, 0, sizeof(*pin)); fdt = bsp_fdt_get(); val = fdt_getprop(fdt, node_offset, property, &len); if (val == NULL || (len % pin_length_bytes != 0) || (index >= len / pin_length_bytes)) { sc = RTEMS_UNSATISFIED; } if (sc == RTEMS_SUCCESSFUL) { pin_nr = fdt32_to_cpu(val[1 + index * pin_length_dwords]); gpio_phandle = fdt32_to_cpu(val[0 + index * pin_length_dwords]); cfgnode = fdt_node_offset_by_phandle(fdt, gpio_phandle); val = fdt_getprop(fdt, cfgnode, "reg", &len); if (len > 0) { gpio_regs = fdt32_to_cpu(val[0]); } else { sc = RTEMS_UNSATISFIED; } } if (sc == RTEMS_SUCCESSFUL) { pin->gpio = imx_gpio_get_by_register((void *)gpio_regs); pin->mask = 1u << pin_nr; pin->shift = pin_nr; pin->mode = mode; } if (sc == RTEMS_SUCCESSFUL) { imx_gpio_init(pin); } return sc; } rtems_vector_number imx_gpio_get_irq_of_node( const void *fdt, int node, size_t index ) { const uint32_t *val; uint32_t pin; int parent; size_t parent_index; int len; val = fdt_getprop(fdt, node, "interrupts", &len); if (val == NULL || len < (int) ((index + 1) * 8)) { return UINT32_MAX; } pin = fdt32_to_cpu(val[index * 2]); if (pin < 16) { parent_index = 0; } else { parent_index = 1; } val = fdt_getprop(fdt, node, "interrupt-parent", &len); if (len != 4) { return UINT32_MAX; } parent = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(val[0])); return imx_get_irq_of_node(fdt, parent, parent_index); } void imx_gpio_init (struct imx_gpio_pin *pin) { switch (pin->mode) { case (IMX_GPIO_MODE_INTERRUPT_LOW): imx_gpio_direction_input(pin); imx_gpio_set_interrupt_mode(pin, IMX_GPIO_ICR_LOW_LEVEL); break; case (IMX_GPIO_MODE_INTERRUPT_HIGH): imx_gpio_direction_input(pin); imx_gpio_set_interrupt_mode(pin, IMX_GPIO_ICR_HIGH_LEVEL); break; case (IMX_GPIO_MODE_INTERRUPT_RISING): imx_gpio_direction_input(pin); imx_gpio_set_interrupt_mode(pin, IMX_GPIO_ICR_RISING_EDGE); break; case (IMX_GPIO_MODE_INTERRUPT_FALLING): imx_gpio_direction_input(pin); imx_gpio_set_interrupt_mode(pin, IMX_GPIO_ICR_FALLING_EDGE); break; case (IMX_GPIO_MODE_INTERRUPT_ANY_EDGE): imx_gpio_direction_input(pin); imx_gpio_set_interrupt_any_edge(pin); /* Interrupt mode isn't really relevant here. Just set it to get * a defined behaviour in case of a bug. */ imx_gpio_set_interrupt_mode(pin, IMX_GPIO_ICR_FALLING_EDGE); break; case (IMX_GPIO_MODE_INPUT): imx_gpio_direction_input(pin); break; case (IMX_GPIO_MODE_OUTPUT): imx_gpio_direction_output(pin); break; default: assert(false); break; } } void imx_gpio_set_output(struct imx_gpio_pin *pin, uint32_t set) { rtems_interrupt_lock_context lock_context; set <<= pin->shift; set &= pin->mask; rtems_interrupt_lock_acquire(&pin->gpio->lock, &lock_context); pin->gpio->regs->dr = (pin->gpio->regs->dr & ~pin->mask) | set; rtems_interrupt_lock_release(&pin->gpio->lock, &lock_context); } void imx_gpio_toggle_output(struct imx_gpio_pin *pin) { rtems_interrupt_lock_context lock_context; rtems_interrupt_lock_acquire(&pin->gpio->lock, &lock_context); pin->gpio->regs->dr = (pin->gpio->regs->dr ^ pin->mask); rtems_interrupt_lock_release(&pin->gpio->lock, &lock_context); } uint32_t imx_gpio_get_input(struct imx_gpio_pin *pin) { return (pin->gpio->regs->dr & pin->mask) >> pin->shift; } void imx_gpio_int_disable(struct imx_gpio_pin *pin) { rtems_interrupt_lock_context lock_context; rtems_interrupt_lock_acquire(&pin->gpio->lock, &lock_context); pin->gpio->regs->imr &= ~pin->mask; rtems_interrupt_lock_release(&pin->gpio->lock, &lock_context); } void imx_gpio_int_enable(struct imx_gpio_pin *pin) { rtems_interrupt_lock_context lock_context; rtems_interrupt_lock_acquire(&pin->gpio->lock, &lock_context); pin->gpio->regs->imr |= pin->mask; rtems_interrupt_lock_release(&pin->gpio->lock, &lock_context); } uint32_t imx_gpio_get_isr(struct imx_gpio_pin *pin) { return (pin->gpio->regs->isr & pin->mask) >> pin->shift; } void imx_gpio_clear_isr(struct imx_gpio_pin *pin, uint32_t clr) { pin->gpio->regs->isr = (clr << pin->shift) & pin->mask; } RTEMS_SYSINIT_ITEM( imx_gpio_attach, RTEMS_SYSINIT_DEVICE_DRIVERS, RTEMS_SYSINIT_ORDER_FIRST );