162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Raspberry Pi 3 expander GPIO driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Uses the firmware mailbox service to communicate with the 662306a36Sopenharmony_ci * GPIO expander on the VPU. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Copyright (C) 2017 Raspberry Pi Trading Ltd. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/gpio/driver.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <soc/bcm2835/raspberrypi-firmware.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define MODULE_NAME "raspberrypi-exp-gpio" 1862306a36Sopenharmony_ci#define NUM_GPIO 8 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define RPI_EXP_GPIO_BASE 128 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define RPI_EXP_GPIO_DIR_IN 0 2362306a36Sopenharmony_ci#define RPI_EXP_GPIO_DIR_OUT 1 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistruct rpi_exp_gpio { 2662306a36Sopenharmony_ci struct gpio_chip gc; 2762306a36Sopenharmony_ci struct rpi_firmware *fw; 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* VC4 firmware mailbox interface data structures */ 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistruct gpio_set_config { 3362306a36Sopenharmony_ci u32 gpio; 3462306a36Sopenharmony_ci u32 direction; 3562306a36Sopenharmony_ci u32 polarity; 3662306a36Sopenharmony_ci u32 term_en; 3762306a36Sopenharmony_ci u32 term_pull_up; 3862306a36Sopenharmony_ci u32 state; 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistruct gpio_get_config { 4262306a36Sopenharmony_ci u32 gpio; 4362306a36Sopenharmony_ci u32 direction; 4462306a36Sopenharmony_ci u32 polarity; 4562306a36Sopenharmony_ci u32 term_en; 4662306a36Sopenharmony_ci u32 term_pull_up; 4762306a36Sopenharmony_ci}; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistruct gpio_get_set_state { 5062306a36Sopenharmony_ci u32 gpio; 5162306a36Sopenharmony_ci u32 state; 5262306a36Sopenharmony_ci}; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic int rpi_exp_gpio_get_polarity(struct gpio_chip *gc, unsigned int off) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci struct rpi_exp_gpio *gpio; 5762306a36Sopenharmony_ci struct gpio_get_config get; 5862306a36Sopenharmony_ci int ret; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci gpio = gpiochip_get_data(gc); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci get.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_CONFIG, 6562306a36Sopenharmony_ci &get, sizeof(get)); 6662306a36Sopenharmony_ci if (ret || get.gpio != 0) { 6762306a36Sopenharmony_ci dev_err(gc->parent, "Failed to get GPIO %u config (%d %x)\n", 6862306a36Sopenharmony_ci off, ret, get.gpio); 6962306a36Sopenharmony_ci return ret ? ret : -EIO; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci return get.polarity; 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic int rpi_exp_gpio_dir_in(struct gpio_chip *gc, unsigned int off) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct rpi_exp_gpio *gpio; 7762306a36Sopenharmony_ci struct gpio_set_config set_in; 7862306a36Sopenharmony_ci int ret; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci gpio = gpiochip_get_data(gc); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci set_in.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 8362306a36Sopenharmony_ci set_in.direction = RPI_EXP_GPIO_DIR_IN; 8462306a36Sopenharmony_ci set_in.term_en = 0; /* termination disabled */ 8562306a36Sopenharmony_ci set_in.term_pull_up = 0; /* n/a as termination disabled */ 8662306a36Sopenharmony_ci set_in.state = 0; /* n/a as configured as an input */ 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci ret = rpi_exp_gpio_get_polarity(gc, off); 8962306a36Sopenharmony_ci if (ret < 0) 9062306a36Sopenharmony_ci return ret; 9162306a36Sopenharmony_ci set_in.polarity = ret; /* Retain existing setting */ 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_CONFIG, 9462306a36Sopenharmony_ci &set_in, sizeof(set_in)); 9562306a36Sopenharmony_ci if (ret || set_in.gpio != 0) { 9662306a36Sopenharmony_ci dev_err(gc->parent, "Failed to set GPIO %u to input (%d %x)\n", 9762306a36Sopenharmony_ci off, ret, set_in.gpio); 9862306a36Sopenharmony_ci return ret ? ret : -EIO; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci return 0; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic int rpi_exp_gpio_dir_out(struct gpio_chip *gc, unsigned int off, int val) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci struct rpi_exp_gpio *gpio; 10662306a36Sopenharmony_ci struct gpio_set_config set_out; 10762306a36Sopenharmony_ci int ret; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci gpio = gpiochip_get_data(gc); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci set_out.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 11262306a36Sopenharmony_ci set_out.direction = RPI_EXP_GPIO_DIR_OUT; 11362306a36Sopenharmony_ci set_out.term_en = 0; /* n/a as an output */ 11462306a36Sopenharmony_ci set_out.term_pull_up = 0; /* n/a as termination disabled */ 11562306a36Sopenharmony_ci set_out.state = val; /* Output state */ 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci ret = rpi_exp_gpio_get_polarity(gc, off); 11862306a36Sopenharmony_ci if (ret < 0) 11962306a36Sopenharmony_ci return ret; 12062306a36Sopenharmony_ci set_out.polarity = ret; /* Retain existing setting */ 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_CONFIG, 12362306a36Sopenharmony_ci &set_out, sizeof(set_out)); 12462306a36Sopenharmony_ci if (ret || set_out.gpio != 0) { 12562306a36Sopenharmony_ci dev_err(gc->parent, "Failed to set GPIO %u to output (%d %x)\n", 12662306a36Sopenharmony_ci off, ret, set_out.gpio); 12762306a36Sopenharmony_ci return ret ? ret : -EIO; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci return 0; 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic int rpi_exp_gpio_get_direction(struct gpio_chip *gc, unsigned int off) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci struct rpi_exp_gpio *gpio; 13562306a36Sopenharmony_ci struct gpio_get_config get; 13662306a36Sopenharmony_ci int ret; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci gpio = gpiochip_get_data(gc); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci get.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_CONFIG, 14362306a36Sopenharmony_ci &get, sizeof(get)); 14462306a36Sopenharmony_ci if (ret || get.gpio != 0) { 14562306a36Sopenharmony_ci dev_err(gc->parent, 14662306a36Sopenharmony_ci "Failed to get GPIO %u config (%d %x)\n", off, ret, 14762306a36Sopenharmony_ci get.gpio); 14862306a36Sopenharmony_ci return ret ? ret : -EIO; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci if (get.direction) 15162306a36Sopenharmony_ci return GPIO_LINE_DIRECTION_OUT; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci return GPIO_LINE_DIRECTION_IN; 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic int rpi_exp_gpio_get(struct gpio_chip *gc, unsigned int off) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci struct rpi_exp_gpio *gpio; 15962306a36Sopenharmony_ci struct gpio_get_set_state get; 16062306a36Sopenharmony_ci int ret; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci gpio = gpiochip_get_data(gc); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci get.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 16562306a36Sopenharmony_ci get.state = 0; /* storage for returned value */ 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_STATE, 16862306a36Sopenharmony_ci &get, sizeof(get)); 16962306a36Sopenharmony_ci if (ret || get.gpio != 0) { 17062306a36Sopenharmony_ci dev_err(gc->parent, 17162306a36Sopenharmony_ci "Failed to get GPIO %u state (%d %x)\n", off, ret, 17262306a36Sopenharmony_ci get.gpio); 17362306a36Sopenharmony_ci return ret ? ret : -EIO; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci return !!get.state; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic void rpi_exp_gpio_set(struct gpio_chip *gc, unsigned int off, int val) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci struct rpi_exp_gpio *gpio; 18162306a36Sopenharmony_ci struct gpio_get_set_state set; 18262306a36Sopenharmony_ci int ret; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci gpio = gpiochip_get_data(gc); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci set.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 18762306a36Sopenharmony_ci set.state = val; /* Output state */ 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_STATE, 19062306a36Sopenharmony_ci &set, sizeof(set)); 19162306a36Sopenharmony_ci if (ret || set.gpio != 0) 19262306a36Sopenharmony_ci dev_err(gc->parent, 19362306a36Sopenharmony_ci "Failed to set GPIO %u state (%d %x)\n", off, ret, 19462306a36Sopenharmony_ci set.gpio); 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic int rpi_exp_gpio_probe(struct platform_device *pdev) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci struct device *dev = &pdev->dev; 20062306a36Sopenharmony_ci struct device_node *np = dev->of_node; 20162306a36Sopenharmony_ci struct device_node *fw_node; 20262306a36Sopenharmony_ci struct rpi_firmware *fw; 20362306a36Sopenharmony_ci struct rpi_exp_gpio *rpi_gpio; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci fw_node = of_get_parent(np); 20662306a36Sopenharmony_ci if (!fw_node) { 20762306a36Sopenharmony_ci dev_err(dev, "Missing firmware node\n"); 20862306a36Sopenharmony_ci return -ENOENT; 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci fw = devm_rpi_firmware_get(&pdev->dev, fw_node); 21262306a36Sopenharmony_ci of_node_put(fw_node); 21362306a36Sopenharmony_ci if (!fw) 21462306a36Sopenharmony_ci return -EPROBE_DEFER; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci rpi_gpio = devm_kzalloc(dev, sizeof(*rpi_gpio), GFP_KERNEL); 21762306a36Sopenharmony_ci if (!rpi_gpio) 21862306a36Sopenharmony_ci return -ENOMEM; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci rpi_gpio->fw = fw; 22162306a36Sopenharmony_ci rpi_gpio->gc.parent = dev; 22262306a36Sopenharmony_ci rpi_gpio->gc.label = MODULE_NAME; 22362306a36Sopenharmony_ci rpi_gpio->gc.owner = THIS_MODULE; 22462306a36Sopenharmony_ci rpi_gpio->gc.base = -1; 22562306a36Sopenharmony_ci rpi_gpio->gc.ngpio = NUM_GPIO; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci rpi_gpio->gc.direction_input = rpi_exp_gpio_dir_in; 22862306a36Sopenharmony_ci rpi_gpio->gc.direction_output = rpi_exp_gpio_dir_out; 22962306a36Sopenharmony_ci rpi_gpio->gc.get_direction = rpi_exp_gpio_get_direction; 23062306a36Sopenharmony_ci rpi_gpio->gc.get = rpi_exp_gpio_get; 23162306a36Sopenharmony_ci rpi_gpio->gc.set = rpi_exp_gpio_set; 23262306a36Sopenharmony_ci rpi_gpio->gc.can_sleep = true; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci return devm_gpiochip_add_data(dev, &rpi_gpio->gc, rpi_gpio); 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic const struct of_device_id rpi_exp_gpio_ids[] = { 23862306a36Sopenharmony_ci { .compatible = "raspberrypi,firmware-gpio" }, 23962306a36Sopenharmony_ci { } 24062306a36Sopenharmony_ci}; 24162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, rpi_exp_gpio_ids); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic struct platform_driver rpi_exp_gpio_driver = { 24462306a36Sopenharmony_ci .driver = { 24562306a36Sopenharmony_ci .name = MODULE_NAME, 24662306a36Sopenharmony_ci .of_match_table = rpi_exp_gpio_ids, 24762306a36Sopenharmony_ci }, 24862306a36Sopenharmony_ci .probe = rpi_exp_gpio_probe, 24962306a36Sopenharmony_ci}; 25062306a36Sopenharmony_cimodule_platform_driver(rpi_exp_gpio_driver); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 25362306a36Sopenharmony_ciMODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.org>"); 25462306a36Sopenharmony_ciMODULE_DESCRIPTION("Raspberry Pi 3 expander GPIO driver"); 25562306a36Sopenharmony_ciMODULE_ALIAS("platform:rpi-exp-gpio"); 256