| /* |
| * Nexus GPIO(s) resolution API |
| * |
| * Copyright (C) 2015-2016, Broadcom Corporation |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * A copy of the GPL is available at |
| * http://www.broadcom.com/licenses/GPLv2.php or from the Free Software |
| * Foundation at https://www.gnu.org/licenses/ . |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/export.h> |
| #include <linux/gpio.h> |
| #include <linux/bitmap.h> |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_address.h> |
| #include <linux/basic_mmio_gpio.h> |
| |
| #include <linux/brcmstb/brcmstb.h> |
| #include <linux/brcmstb/gpio_api.h> |
| |
| #include "gpio.h" |
| #define GIO_DATA_OFFSET 4 |
| #define GIO_DIR_OFFSET 8 |
| |
| static const char *brcmstb_gpio_compat = GPIO_DT_COMPAT; |
| |
| static int brcmstb_gpio_chip_find(struct gpio_chip *chip, void *data) |
| { |
| struct device_node *dn = data; |
| |
| if (chip->of_node == dn) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Keep an additional bitmap of which GPIOs are requested by Nexus to |
| * maintain Linux/Nexus GPIO exclusive ownership managed via gpio_request(). |
| * We do want multiple consecutive calls to brcmstb_gpio_irq() not to fail |
| * because the first one claimed ownernship already. |
| */ |
| static DECLARE_BITMAP(brcmstb_gpio_requested, ARCH_NR_GPIOS); |
| |
| static int brcmstb_gpio_request(unsigned int gpio) |
| { |
| int ret = 0; |
| |
| /* Request the GPIO to flag that Nexus now owns it, but |
| * also keep it requested in our local bitmap to avoid |
| * subsequent gpio_request() calls to the same GPIO |
| */ |
| if (!test_bit(gpio, brcmstb_gpio_requested)) { |
| ret = gpio_request(gpio, "nexus"); |
| if (ret) { |
| pr_err("%s: GPIO request failed\n", __func__); |
| return ret; |
| } |
| |
| /* We could get ownership, so flag it now */ |
| set_bit(gpio, brcmstb_gpio_requested); |
| } |
| |
| return ret; |
| } |
| |
| static int brcmstb_gpio_find_base_by_addr(uint32_t addr, phys_addr_t *start) |
| { |
| struct device_node *dn; |
| struct gpio_chip *gc; |
| struct resource res; |
| int ret; |
| |
| for_each_compatible_node(dn, NULL, brcmstb_gpio_compat) { |
| ret = of_address_to_resource(dn, 0, &res); |
| if (ret) { |
| pr_err("%s: unable to translate resource\n", __func__); |
| continue; |
| } |
| |
| if (res.flags != IORESOURCE_MEM) { |
| pr_err("%s: invalid resource type\n", __func__); |
| continue; |
| } |
| |
| /* Mask address with the physical offset, since the address has |
| * already been translated based on the busing hierarchy |
| */ |
| res.start &= ~BCHP_PHYSICAL_OFFSET; |
| res.end &= ~BCHP_PHYSICAL_OFFSET; |
| |
| /* Verify address in the resource range, if not, go to the |
| * other GPIO controllers |
| */ |
| if (addr < res.start || addr >= res.end) |
| continue; |
| |
| gc = gpiochip_find(dn, brcmstb_gpio_chip_find); |
| if (!gc) { |
| pr_err("%s: unable to find gpio chip\n", __func__); |
| continue; |
| } |
| |
| if (start) |
| *start = (phys_addr_t)res.start; |
| |
| return gc->base; |
| } |
| |
| return -ENODEV; |
| |
| } |
| |
| static int brcmstb_gpio_find_by_addr(uint32_t addr, unsigned int shift) |
| { |
| int gpio, gpio_base; |
| phys_addr_t start, bank_offset; |
| |
| gpio_base = brcmstb_gpio_find_base_by_addr(addr, &start); |
| if (gpio_base >= 0) { |
| /* Now find out what GPIO bank this pin belongs to */ |
| bank_offset = ((phys_addr_t)addr - start) / GIO_BANK_SIZE; |
| |
| gpio = gpio_base + shift + bank_offset * GPIO_PER_BANK; |
| |
| pr_debug("%s: xlate base=%d, offset=%d, shift=%d, gpio=%d\n", |
| __func__, (int)gpio_base, (int)bank_offset, shift, |
| (int)(gpio - gpio_base)); |
| |
| return gpio; |
| } |
| |
| return gpio_base; |
| } |
| |
| int brcmstb_gpio_update32(uint32_t addr, uint32_t mask, uint32_t value) |
| { |
| struct bgpio_chip *bgc; |
| struct gpio_chip *gc; |
| int ret, bit, gpio_base; |
| phys_addr_t start, offset, bank_offset; |
| unsigned long flags; |
| uint32_t ivalue; |
| void __iomem *reg; |
| |
| gpio_base = brcmstb_gpio_find_base_by_addr(addr, &start); |
| if (gpio_base < 0) { |
| pr_err("%s: addr is not in GPIO range\n", __func__); |
| return -EPERM; |
| } |
| |
| /* Now find out what GPIO bank this pin belongs to */ |
| offset = (phys_addr_t)addr - start; |
| bank_offset = offset / GIO_BANK_SIZE; |
| offset -= bank_offset * GIO_BANK_SIZE; |
| |
| pr_debug("%s: xlate base=%d, offset=%d, gpio=%d\n", |
| __func__, (int)gpio_base, (int)bank_offset, |
| (int)(bank_offset * GPIO_PER_BANK)); |
| |
| gpio_base += bank_offset * GPIO_PER_BANK; |
| |
| for (bit = 0; bit < GPIO_PER_BANK; bit++) { |
| /* Ignore bits which are not in mask */ |
| if (!((1 << bit) & mask)) |
| continue; |
| |
| ret = brcmstb_gpio_request(gpio_base + bit); |
| if (ret < 0) { |
| pr_err("%s: unable to request gpio %d\n", |
| __func__, gpio_base + bit); |
| return ret; |
| } |
| } |
| |
| /* We got full access to the entire mask, do the write */ |
| |
| pr_info("%s: offset=0x%08x mask=0x%08x, value=0x%08x\n", |
| __func__, addr, mask, value); |
| |
| gc = gpiod_to_chip(gpio_to_desc(gpio_base)); |
| if (gc == NULL) { |
| pr_err("%s: unable to resolve gpio chip\n", __func__); |
| return -EPERM; |
| } |
| bgc = to_bgpio_chip(gc); |
| reg = bgc->reg_dat; |
| if (reg == 0) { |
| pr_err("%s: unable to resolve GIO mapped address\n", __func__); |
| return -EPERM; |
| } |
| |
| spin_lock_irqsave(&bgc->lock, flags); |
| ivalue = bgc->read_reg(reg + offset - GIO_DATA_OFFSET); |
| ivalue &= ~(mask); |
| ivalue |= (value & mask); |
| |
| /* update shadows */ |
| switch (offset) { |
| case GIO_DATA_OFFSET: |
| bgc->data = ivalue; |
| break; |
| case GIO_DIR_OFFSET: |
| bgc->dir = ivalue; |
| break; |
| default: |
| break; |
| } |
| |
| bgc->write_reg(reg + offset - GIO_DATA_OFFSET, ivalue); |
| spin_unlock_irqrestore(&bgc->lock, flags); |
| |
| return 0; |
| } |
| |
| int brcmstb_gpio_irq(uint32_t addr, unsigned int shift) |
| { |
| int gpio, ret; |
| |
| gpio = brcmstb_gpio_find_by_addr(addr, shift); |
| if (gpio < 0) |
| return gpio; |
| |
| ret = brcmstb_gpio_request(gpio); |
| if (ret < 0) |
| return ret; |
| |
| ret = gpio_to_irq(gpio); |
| if (ret < 0) { |
| gpio_free(gpio); |
| pr_err("%s: unable to map GPIO%d to irq, ret=%d\n", |
| __func__, gpio, ret); |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(brcmstb_gpio_irq); |
| |
| static void brcmstb_gpio_free(unsigned int gpio) |
| { |
| if (test_bit(gpio, brcmstb_gpio_requested)) { |
| gpio_free(gpio); |
| clear_bit(gpio, brcmstb_gpio_requested); |
| } |
| } |
| |
| void brcmstb_gpio_remove(void) |
| { |
| unsigned i; |
| |
| for (i = 0; i < ARCH_NR_GPIOS; i++) |
| { |
| brcmstb_gpio_free(i); |
| } |
| } |
| EXPORT_SYMBOL(brcmstb_gpio_remove); |