From 33810c078d48da7e628b74ef6537a589f41bee2b Mon Sep 17 00:00:00 2001 From: popcornmix Date: Tue, 23 Feb 2016 19:56:04 +0000 Subject: [PATCH] bcm2835-virtgpio: Virtual GPIO driver Add a virtual GPIO driver that uses the firmware mailbox interface to request that the VPU toggles LEDs. gpio: bcm-virt: Fix the get() method The get() method does not understand the on-the-wire encoding of the remote GPIO states, thinking they are simple on/off bits when they are really pairs of 16-bit counts. Rewrite the get() handler to return the value last written, which will eventually match the actual GPIO state if there are no other changes. See: https://github.com/raspberrypi/linux/issues/4638 Signed-off-by: Phil Elwell --- drivers/gpio/Kconfig | 6 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-bcm-virt.c | 214 +++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 drivers/gpio/gpio-bcm-virt.c --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -194,6 +194,12 @@ config GPIO_BCM_XGS_IPROC help Say yes here to enable GPIO support for Broadcom XGS iProc SoCs. +config GPIO_BCM_VIRT + bool "Broadcom Virt GPIO" + depends on OF_GPIO && RASPBERRYPI_FIRMWARE && (ARCH_BCM2835 || COMPILE_TEST) + help + Turn on virtual GPIO support for Broadcom BCM283X chips. + config GPIO_BRCMSTB tristate "BRCMSTB GPIO support" default y if (ARCH_BRCMSTB || BMIPS_GENERIC) --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_GPIO_ASPEED_SGPIO) += gpio obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o +obj-$(CONFIG_GPIO_BCM_VIRT) += gpio-bcm-virt.o obj-$(CONFIG_GPIO_BD71815) += gpio-bd71815.o obj-$(CONFIG_GPIO_BD71828) += gpio-bd71828.o obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o --- /dev/null +++ b/drivers/gpio/gpio-bcm-virt.c @@ -0,0 +1,214 @@ +/* + * brcmvirt GPIO driver + * + * Copyright (C) 2012,2013 Dom Cobley + * Based on gpio-clps711x.c by Alexander Shiyan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "brcmvirt-gpio" +#define NUM_GPIO 2 + +struct brcmvirt_gpio { + struct gpio_chip gc; + u32 __iomem *ts_base; + /* two packed 16-bit counts of enabled and disables + Allows host to detect a brief enable that was missed */ + u32 enables_disables[NUM_GPIO]; + dma_addr_t bus_addr; +}; + +static int brcmvirt_gpio_dir_in(struct gpio_chip *gc, unsigned off) +{ + struct brcmvirt_gpio *gpio; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + return -EINVAL; +} + +static int brcmvirt_gpio_dir_out(struct gpio_chip *gc, unsigned off, int val) +{ + struct brcmvirt_gpio *gpio; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + return 0; +} + +static int brcmvirt_gpio_get(struct gpio_chip *gc, unsigned off) +{ + struct brcmvirt_gpio *gpio; + unsigned v; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + v = readl(gpio->ts_base + off); + return (s16)((v >> 16) - v) > 0; +} + +static void brcmvirt_gpio_set(struct gpio_chip *gc, unsigned off, int val) +{ + struct brcmvirt_gpio *gpio; + u16 enables, disables; + s16 diff; + bool lit; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + enables = gpio->enables_disables[off] >> 16; + disables = gpio->enables_disables[off] >> 0; + diff = (s16)(enables - disables); + lit = diff > 0; + if ((val && lit) || (!val && !lit)) + return; + if (val) + enables++; + else + disables++; + diff = (s16)(enables - disables); + BUG_ON(diff != 0 && diff != 1); + gpio->enables_disables[off] = (enables << 16) | (disables << 0); + writel(gpio->enables_disables[off], gpio->ts_base + off); +} + +static int brcmvirt_gpio_probe(struct platform_device *pdev) +{ + int err = 0; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *fw_node; + struct rpi_firmware *fw; + struct brcmvirt_gpio *ucb; + u32 gpiovirtbuf; + + fw_node = of_parse_phandle(np, "firmware", 0); + if (!fw_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + fw = rpi_firmware_get(fw_node); + if (!fw) + return -EPROBE_DEFER; + + ucb = devm_kzalloc(dev, sizeof *ucb, GFP_KERNEL); + if (!ucb) { + err = -EINVAL; + goto out; + } + + ucb->ts_base = dma_alloc_coherent(dev, PAGE_SIZE, &ucb->bus_addr, GFP_KERNEL); + if (!ucb->ts_base) { + pr_err("[%s]: failed to dma_alloc_coherent(%ld)\n", + __func__, PAGE_SIZE); + err = -ENOMEM; + goto out; + } + + gpiovirtbuf = (u32)ucb->bus_addr; + err = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_SET_GPIOVIRTBUF, + &gpiovirtbuf, sizeof(gpiovirtbuf)); + + if (err || gpiovirtbuf != 0) { + dev_warn(dev, "Failed to set gpiovirtbuf, trying to get err:%x\n", err); + dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr); + ucb->ts_base = 0; + ucb->bus_addr = 0; + } + + if (!ucb->ts_base) { + err = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_GET_GPIOVIRTBUF, + &gpiovirtbuf, sizeof(gpiovirtbuf)); + + if (err) { + dev_err(dev, "Failed to get gpiovirtbuf\n"); + goto out; + } + + if (!gpiovirtbuf) { + dev_err(dev, "No virtgpio buffer\n"); + err = -ENOENT; + goto out; + } + + // mmap the physical memory + gpiovirtbuf &= ~0xc0000000; + ucb->ts_base = ioremap(gpiovirtbuf, 4096); + if (ucb->ts_base == NULL) { + dev_err(dev, "Failed to map physical address\n"); + err = -ENOENT; + goto out; + } + ucb->bus_addr = 0; + } + ucb->gc.label = MODULE_NAME; + ucb->gc.owner = THIS_MODULE; + //ucb->gc.dev = dev; + ucb->gc.of_node = np; + ucb->gc.base = 100; + ucb->gc.ngpio = NUM_GPIO; + + ucb->gc.direction_input = brcmvirt_gpio_dir_in; + ucb->gc.direction_output = brcmvirt_gpio_dir_out; + ucb->gc.get = brcmvirt_gpio_get; + ucb->gc.set = brcmvirt_gpio_set; + ucb->gc.can_sleep = true; + + err = gpiochip_add(&ucb->gc); + if (err) + goto out; + + platform_set_drvdata(pdev, ucb); + + return 0; +out: + if (ucb->bus_addr) { + dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr); + ucb->bus_addr = 0; + ucb->ts_base = NULL; + } else if (ucb->ts_base) { + iounmap(ucb->ts_base); + ucb->ts_base = NULL; + } + return err; +} + +static int brcmvirt_gpio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int err = 0; + struct brcmvirt_gpio *ucb = platform_get_drvdata(pdev); + + gpiochip_remove(&ucb->gc); + if (ucb->bus_addr) + dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr); + else if (ucb->ts_base) + iounmap(ucb->ts_base); + return err; +} + +static const struct of_device_id __maybe_unused brcmvirt_gpio_ids[] = { + { .compatible = "brcm,bcm2835-virtgpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, brcmvirt_gpio_ids); + +static struct platform_driver brcmvirt_gpio_driver = { + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(brcmvirt_gpio_ids), + }, + .probe = brcmvirt_gpio_probe, + .remove = brcmvirt_gpio_remove, +}; +module_platform_driver(brcmvirt_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dom Cobley "); +MODULE_DESCRIPTION("brcmvirt GPIO driver"); +MODULE_ALIAS("platform:brcmvirt-gpio");