diff --git a/arch/arm/mach-bcm283x/include/mach/mbox.h b/arch/arm/mach-bcm283x/include/mach/mbox.h index 6db961b..f10f5bc 100644 --- a/arch/arm/mach-bcm283x/include/mach/mbox.h +++ b/arch/arm/mach-bcm283x/include/mach/mbox.h @@ -126,6 +126,10 @@ */ #define BCM2835_MBOX_TAG_GET_BOARD_REV 0x00010002 +#define BCM2835_MBOX_TAG_GET_GPIO_STATE 0x00030041 +#define BCM2835_MBOX_TAG_SET_GPIO_STATE 0x00038041 +#define BCM2835_MBOX_TAG_GET_GPIO_CONFIG 0x00030043 +#define BCM2835_MBOX_TAG_SET_GPIO_CONFIG 0x00038043 /* * ids diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 0f924d1..7e4fc90 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -129,6 +129,13 @@ help Say yes here to support the PrimeCell PL061 GPIO device +config GPIO_RASPBERRYPI_EXP + bool "Raspberry Pi 3 GPIO Expander" + depends on ARCH_BCM283X + help + Turn on GPIO support for the expander on Raspberry Pi 3 boards, using + the firmware mailbox to communicate with VideoCore on BCM283x chips. + config GPIO_STMPE depends on MFD_STMPE bool "STMPE GPIO Expander" diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index bc5c500..77dcf58 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -22,3 +22,4 @@ obj-$(CONFIG_GPIO_DESIGNWARE) += gpio-dw.o obj-$(CONFIG_GPIO_SX150X) += gpio-sx150x.o obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o +obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o diff --git a/drivers/gpio/gpio-raspberrypi-exp.c b/drivers/gpio/gpio-raspberrypi-exp.c new file mode 100644 index 0000000..0713e3c --- /dev/null +++ b/drivers/gpio/gpio-raspberrypi-exp.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Raspberry Pi 3 expander GPIO driver + * + * Uses the firmware mailbox service to communicate with the + * GPIO expander on the VPU. + * + * Copyright (C) 2017 Raspberry Pi Trading Ltd. + */ + +#include +#include +#include +#include + +#define NUM_GPIO 8 + +#define RPI_EXP_GPIO_BASE 128 + +#define RPI_EXP_GPIO_DIR_IN 0 +#define RPI_EXP_GPIO_DIR_OUT 1 + +struct rpi_exp_gpio { + struct gpio_chip gc; + struct rpi_firmware *fw; +}; + +/* VC4 firmware mailbox interface data structures */ +struct gpio_set_config { + struct bcm2835_mbox_hdr hdr; /* buf_size, code */ + struct bcm2835_mbox_tag_hdr tag_hdr; /* tag, val_buf_size, val_len */ + union { + struct { + u32 gpio; + u32 direction; + u32 polarity; + u32 term_en; + u32 term_pull_up; + u32 state; + } req; + } body; + u32 end_tag; +}; + +struct gpio_get_config { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + u32 gpio; + u32 direction; + u32 polarity; + u32 term_en; + u32 term_pull_up; + } req; + } body; + u32 end_tag; +}; + +struct gpio_get_set_state { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + u32 gpio; + u32 state; + } req; + } body; + u32 end_tag; +}; + +static inline struct rpi_exp_gpio *to_rpi_gpio(struct gpio_chip *gc) +{ + return container_of(gc, struct rpi_exp_gpio, gc); +} + +static int rpi_exp_gpio_get_polarity(struct gpio_chip *gc, unsigned int off) +{ + struct rpi_exp_gpio *gpio; + int ret; + BCM2835_MBOX_STACK_ALIGN(struct gpio_get_config, get); + BCM2835_MBOX_INIT_HDR(get); + BCM2835_MBOX_INIT_TAG(get, GET_GPIO_CONFIG); + + gpio = to_rpi_gpio(gc); + + get->body.req.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ + + ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &get->hdr); + if (ret || get->body.req.gpio != 0) { + dev_err(gc->dev, "Failed to get GPIO %u config (%d %x)\n", + off, ret, get->body.req.gpio); + return ret ? ret : -EIO; + } + return get->body.req.polarity; +} + +static int rpi_exp_gpio_dir_in(struct gpio_chip *gc, unsigned int off) +{ + struct rpi_exp_gpio *gpio; + int ret; + BCM2835_MBOX_STACK_ALIGN(struct gpio_set_config, set_in); + BCM2835_MBOX_INIT_HDR(set_in); + BCM2835_MBOX_INIT_TAG(set_in, SET_GPIO_CONFIG); + + gpio = to_rpi_gpio(gc); + + set_in->body.req.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ + set_in->body.req.direction = RPI_EXP_GPIO_DIR_IN; + set_in->body.req.term_en = 0; /* termination disabled */ + set_in->body.req.term_pull_up = 0; /* n/a as termination disabled */ + set_in->body.req.state = 0; /* n/a as configured as an input */ + + ret = rpi_exp_gpio_get_polarity(gc, off); + if (ret < 0) + return ret; + + set_in->body.req.polarity = ret; /* Retain existing setting */ + + ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &set_in->hdr); + if (ret || set_in->body.req.gpio != 0) { + dev_err(gc->dev, "Failed to set GPIO %u to input (%d %x)\n", + off, ret, set_in->body.req.gpio); + return ret ? ret : -EIO; + } + + return 0; +} + +static int rpi_exp_gpio_dir_out(struct gpio_chip *gc, unsigned int off, int val) +{ + struct rpi_exp_gpio *gpio; + int ret; + BCM2835_MBOX_STACK_ALIGN(struct gpio_set_config, set_out); + BCM2835_MBOX_INIT_HDR(set_out); + BCM2835_MBOX_INIT_TAG(set_out, SET_GPIO_CONFIG); + + gpio = to_rpi_gpio(gc); + + set_out->body.req.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ + set_out->body.req.direction = RPI_EXP_GPIO_DIR_OUT; + set_out->body.req.term_en = 0; /* n/a as an output */ + set_out->body.req.term_pull_up = 0; /* n/a as termination disabled */ + set_out->body.req.state = val; /* Output state */ + + ret = rpi_exp_gpio_get_polarity(gc, off); + if (ret < 0) + return ret; + set_out->body.req.polarity = ret; /* Retain existing setting */ + + ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &set_out->hdr); + if (ret || set_out->body.req.gpio != 0) { + dev_err(gc->dev, "Failed to set GPIO %u to output (%d %x)\n", + off, ret, set_out->body.req.gpio); + return ret ? ret : -EIO; + } + return 0; +} + +static int rpi_exp_gpio_get_direction(struct gpio_chip *gc, unsigned int off) +{ + struct rpi_exp_gpio *gpio; + int ret; + BCM2835_MBOX_STACK_ALIGN(struct gpio_get_config, get); + BCM2835_MBOX_INIT_HDR(get); + BCM2835_MBOX_INIT_TAG(get, GET_GPIO_CONFIG); + + gpio = to_rpi_gpio(gc); + + get->body.req.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ + + ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &get->hdr); + if (ret || get->body.req.gpio != 0) { + dev_err(gc->dev, + "Failed to get GPIO %u config (%d %x)\n", off, ret, + get->body.req.gpio); + return ret ? ret : -EIO; + } + if (get->body.req.direction) + return GPIOF_DIR_OUT; + + return GPIOF_DIR_IN; +} + +static int rpi_exp_gpio_get(struct gpio_chip *gc, unsigned int off) +{ + struct rpi_exp_gpio *gpio; + int ret; + BCM2835_MBOX_STACK_ALIGN(struct gpio_get_set_state, get); + BCM2835_MBOX_INIT_HDR(get); + BCM2835_MBOX_INIT_TAG(get, GET_GPIO_STATE); + + gpio = to_rpi_gpio(gc); + + get->body.req.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ + get->body.req.state = 0; /* storage for returned value */ + + ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &get->hdr); + if (ret || get->body.req.gpio != 0) { + dev_err(gc->dev, + "Failed to get GPIO %u state (%d %x)\n", off, ret, + get->body.req.gpio); + return ret ? ret : -EIO; + } + return !!get->body.req.state; +} + +static void rpi_exp_gpio_set(struct gpio_chip *gc, unsigned int off, int val) +{ + struct rpi_exp_gpio *gpio; + int ret; + BCM2835_MBOX_STACK_ALIGN(struct gpio_get_set_state, set); + BCM2835_MBOX_INIT_HDR(set); + BCM2835_MBOX_INIT_TAG(set, SET_GPIO_STATE); + + gpio = to_rpi_gpio(gc); + + set->body.req.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ + set->body.req.state = val; /* Output state */ + + ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &set->hdr); + if (ret || set->body.req.gpio != 0) + dev_err(gc->dev, + "Failed to set GPIO %u state (%d %x)\n", off, ret, + set->body.req.gpio); +} + +static struct gpio_ops rpi_exp_gpio_ops = { + .direction_input = rpi_exp_gpio_dir_in, + .direction_output = rpi_exp_gpio_dir_out, + .get_direction = rpi_exp_gpio_get_direction, + .get = rpi_exp_gpio_get, + .set = rpi_exp_gpio_set, +}; + +static int rpi_exp_gpio_probe(struct device_d *dev) +{ + struct rpi_exp_gpio *rpi_gpio; + int ret; + + rpi_gpio = xzalloc(sizeof(*rpi_gpio)); + + rpi_gpio->gc.dev = dev; + rpi_gpio->gc.base = -1; + rpi_gpio->gc.ngpio = NUM_GPIO; + rpi_gpio->gc.ops = &rpi_exp_gpio_ops; + + ret = gpiochip_add(&rpi_gpio->gc); + if (ret) + return ret; + + dev_dbg(dev, "probed gpiochip with %d gpios, base %d\n", + rpi_gpio->gc.ngpio, rpi_gpio->gc.base); + + return 0; +} + +static __maybe_unused struct of_device_id rpi_exp_gpio_ids[] = { + { + .compatible = "raspberrypi,firmware-gpio", + }, { + /* sentinel */ + }, +}; + +static struct driver_d rpi_exp_gpio_driver = { + .name = "rpi-exp-gpio", + .probe = rpi_exp_gpio_probe, + .of_compatible = DRV_OF_COMPAT(rpi_exp_gpio_ids), +}; + +device_platform_driver(rpi_exp_gpio_driver);