18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Raspberry Pi 3 expander GPIO driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Uses the firmware mailbox service to communicate with the 68c2ecf20Sopenharmony_ci * GPIO expander on the VPU. 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Copyright (C) 2017 Raspberry Pi Trading Ltd. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/err.h> 128c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci#include <soc/bcm2835/raspberrypi-firmware.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define MODULE_NAME "raspberrypi-exp-gpio" 188c2ecf20Sopenharmony_ci#define NUM_GPIO 8 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define RPI_EXP_GPIO_BASE 128 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define RPI_EXP_GPIO_DIR_IN 0 238c2ecf20Sopenharmony_ci#define RPI_EXP_GPIO_DIR_OUT 1 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistruct rpi_exp_gpio { 268c2ecf20Sopenharmony_ci struct gpio_chip gc; 278c2ecf20Sopenharmony_ci struct rpi_firmware *fw; 288c2ecf20Sopenharmony_ci}; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* VC4 firmware mailbox interface data structures */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistruct gpio_set_config { 338c2ecf20Sopenharmony_ci u32 gpio; 348c2ecf20Sopenharmony_ci u32 direction; 358c2ecf20Sopenharmony_ci u32 polarity; 368c2ecf20Sopenharmony_ci u32 term_en; 378c2ecf20Sopenharmony_ci u32 term_pull_up; 388c2ecf20Sopenharmony_ci u32 state; 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistruct gpio_get_config { 428c2ecf20Sopenharmony_ci u32 gpio; 438c2ecf20Sopenharmony_ci u32 direction; 448c2ecf20Sopenharmony_ci u32 polarity; 458c2ecf20Sopenharmony_ci u32 term_en; 468c2ecf20Sopenharmony_ci u32 term_pull_up; 478c2ecf20Sopenharmony_ci}; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistruct gpio_get_set_state { 508c2ecf20Sopenharmony_ci u32 gpio; 518c2ecf20Sopenharmony_ci u32 state; 528c2ecf20Sopenharmony_ci}; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic int rpi_exp_gpio_get_polarity(struct gpio_chip *gc, unsigned int off) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci struct rpi_exp_gpio *gpio; 578c2ecf20Sopenharmony_ci struct gpio_get_config get; 588c2ecf20Sopenharmony_ci int ret; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci gpio = gpiochip_get_data(gc); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci get.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_CONFIG, 658c2ecf20Sopenharmony_ci &get, sizeof(get)); 668c2ecf20Sopenharmony_ci if (ret || get.gpio != 0) { 678c2ecf20Sopenharmony_ci dev_err(gc->parent, "Failed to get GPIO %u config (%d %x)\n", 688c2ecf20Sopenharmony_ci off, ret, get.gpio); 698c2ecf20Sopenharmony_ci return ret ? ret : -EIO; 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci return get.polarity; 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic int rpi_exp_gpio_dir_in(struct gpio_chip *gc, unsigned int off) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci struct rpi_exp_gpio *gpio; 778c2ecf20Sopenharmony_ci struct gpio_set_config set_in; 788c2ecf20Sopenharmony_ci int ret; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci gpio = gpiochip_get_data(gc); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci set_in.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 838c2ecf20Sopenharmony_ci set_in.direction = RPI_EXP_GPIO_DIR_IN; 848c2ecf20Sopenharmony_ci set_in.term_en = 0; /* termination disabled */ 858c2ecf20Sopenharmony_ci set_in.term_pull_up = 0; /* n/a as termination disabled */ 868c2ecf20Sopenharmony_ci set_in.state = 0; /* n/a as configured as an input */ 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci ret = rpi_exp_gpio_get_polarity(gc, off); 898c2ecf20Sopenharmony_ci if (ret < 0) 908c2ecf20Sopenharmony_ci return ret; 918c2ecf20Sopenharmony_ci set_in.polarity = ret; /* Retain existing setting */ 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_CONFIG, 948c2ecf20Sopenharmony_ci &set_in, sizeof(set_in)); 958c2ecf20Sopenharmony_ci if (ret || set_in.gpio != 0) { 968c2ecf20Sopenharmony_ci dev_err(gc->parent, "Failed to set GPIO %u to input (%d %x)\n", 978c2ecf20Sopenharmony_ci off, ret, set_in.gpio); 988c2ecf20Sopenharmony_ci return ret ? ret : -EIO; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci return 0; 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic int rpi_exp_gpio_dir_out(struct gpio_chip *gc, unsigned int off, int val) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci struct rpi_exp_gpio *gpio; 1068c2ecf20Sopenharmony_ci struct gpio_set_config set_out; 1078c2ecf20Sopenharmony_ci int ret; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci gpio = gpiochip_get_data(gc); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci set_out.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 1128c2ecf20Sopenharmony_ci set_out.direction = RPI_EXP_GPIO_DIR_OUT; 1138c2ecf20Sopenharmony_ci set_out.term_en = 0; /* n/a as an output */ 1148c2ecf20Sopenharmony_ci set_out.term_pull_up = 0; /* n/a as termination disabled */ 1158c2ecf20Sopenharmony_ci set_out.state = val; /* Output state */ 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci ret = rpi_exp_gpio_get_polarity(gc, off); 1188c2ecf20Sopenharmony_ci if (ret < 0) 1198c2ecf20Sopenharmony_ci return ret; 1208c2ecf20Sopenharmony_ci set_out.polarity = ret; /* Retain existing setting */ 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_CONFIG, 1238c2ecf20Sopenharmony_ci &set_out, sizeof(set_out)); 1248c2ecf20Sopenharmony_ci if (ret || set_out.gpio != 0) { 1258c2ecf20Sopenharmony_ci dev_err(gc->parent, "Failed to set GPIO %u to output (%d %x)\n", 1268c2ecf20Sopenharmony_ci off, ret, set_out.gpio); 1278c2ecf20Sopenharmony_ci return ret ? ret : -EIO; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci return 0; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic int rpi_exp_gpio_get_direction(struct gpio_chip *gc, unsigned int off) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci struct rpi_exp_gpio *gpio; 1358c2ecf20Sopenharmony_ci struct gpio_get_config get; 1368c2ecf20Sopenharmony_ci int ret; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci gpio = gpiochip_get_data(gc); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci get.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_CONFIG, 1438c2ecf20Sopenharmony_ci &get, sizeof(get)); 1448c2ecf20Sopenharmony_ci if (ret || get.gpio != 0) { 1458c2ecf20Sopenharmony_ci dev_err(gc->parent, 1468c2ecf20Sopenharmony_ci "Failed to get GPIO %u config (%d %x)\n", off, ret, 1478c2ecf20Sopenharmony_ci get.gpio); 1488c2ecf20Sopenharmony_ci return ret ? ret : -EIO; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci if (get.direction) 1518c2ecf20Sopenharmony_ci return GPIO_LINE_DIRECTION_OUT; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci return GPIO_LINE_DIRECTION_IN; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic int rpi_exp_gpio_get(struct gpio_chip *gc, unsigned int off) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci struct rpi_exp_gpio *gpio; 1598c2ecf20Sopenharmony_ci struct gpio_get_set_state get; 1608c2ecf20Sopenharmony_ci int ret; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci gpio = gpiochip_get_data(gc); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci get.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 1658c2ecf20Sopenharmony_ci get.state = 0; /* storage for returned value */ 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_STATE, 1688c2ecf20Sopenharmony_ci &get, sizeof(get)); 1698c2ecf20Sopenharmony_ci if (ret || get.gpio != 0) { 1708c2ecf20Sopenharmony_ci dev_err(gc->parent, 1718c2ecf20Sopenharmony_ci "Failed to get GPIO %u state (%d %x)\n", off, ret, 1728c2ecf20Sopenharmony_ci get.gpio); 1738c2ecf20Sopenharmony_ci return ret ? ret : -EIO; 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci return !!get.state; 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic void rpi_exp_gpio_set(struct gpio_chip *gc, unsigned int off, int val) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci struct rpi_exp_gpio *gpio; 1818c2ecf20Sopenharmony_ci struct gpio_get_set_state set; 1828c2ecf20Sopenharmony_ci int ret; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci gpio = gpiochip_get_data(gc); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci set.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */ 1878c2ecf20Sopenharmony_ci set.state = val; /* Output state */ 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_STATE, 1908c2ecf20Sopenharmony_ci &set, sizeof(set)); 1918c2ecf20Sopenharmony_ci if (ret || set.gpio != 0) 1928c2ecf20Sopenharmony_ci dev_err(gc->parent, 1938c2ecf20Sopenharmony_ci "Failed to set GPIO %u state (%d %x)\n", off, ret, 1948c2ecf20Sopenharmony_ci set.gpio); 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic int rpi_exp_gpio_probe(struct platform_device *pdev) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2008c2ecf20Sopenharmony_ci struct device_node *np = dev->of_node; 2018c2ecf20Sopenharmony_ci struct device_node *fw_node; 2028c2ecf20Sopenharmony_ci struct rpi_firmware *fw; 2038c2ecf20Sopenharmony_ci struct rpi_exp_gpio *rpi_gpio; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci fw_node = of_get_parent(np); 2068c2ecf20Sopenharmony_ci if (!fw_node) { 2078c2ecf20Sopenharmony_ci dev_err(dev, "Missing firmware node\n"); 2088c2ecf20Sopenharmony_ci return -ENOENT; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci fw = rpi_firmware_get(fw_node); 2128c2ecf20Sopenharmony_ci of_node_put(fw_node); 2138c2ecf20Sopenharmony_ci if (!fw) 2148c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci rpi_gpio = devm_kzalloc(dev, sizeof(*rpi_gpio), GFP_KERNEL); 2178c2ecf20Sopenharmony_ci if (!rpi_gpio) 2188c2ecf20Sopenharmony_ci return -ENOMEM; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci rpi_gpio->fw = fw; 2218c2ecf20Sopenharmony_ci rpi_gpio->gc.parent = dev; 2228c2ecf20Sopenharmony_ci rpi_gpio->gc.label = MODULE_NAME; 2238c2ecf20Sopenharmony_ci rpi_gpio->gc.owner = THIS_MODULE; 2248c2ecf20Sopenharmony_ci rpi_gpio->gc.of_node = np; 2258c2ecf20Sopenharmony_ci rpi_gpio->gc.base = -1; 2268c2ecf20Sopenharmony_ci rpi_gpio->gc.ngpio = NUM_GPIO; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci rpi_gpio->gc.direction_input = rpi_exp_gpio_dir_in; 2298c2ecf20Sopenharmony_ci rpi_gpio->gc.direction_output = rpi_exp_gpio_dir_out; 2308c2ecf20Sopenharmony_ci rpi_gpio->gc.get_direction = rpi_exp_gpio_get_direction; 2318c2ecf20Sopenharmony_ci rpi_gpio->gc.get = rpi_exp_gpio_get; 2328c2ecf20Sopenharmony_ci rpi_gpio->gc.set = rpi_exp_gpio_set; 2338c2ecf20Sopenharmony_ci rpi_gpio->gc.can_sleep = true; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci return devm_gpiochip_add_data(dev, &rpi_gpio->gc, rpi_gpio); 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cistatic const struct of_device_id rpi_exp_gpio_ids[] = { 2398c2ecf20Sopenharmony_ci { .compatible = "raspberrypi,firmware-gpio" }, 2408c2ecf20Sopenharmony_ci { } 2418c2ecf20Sopenharmony_ci}; 2428c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, rpi_exp_gpio_ids); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cistatic struct platform_driver rpi_exp_gpio_driver = { 2458c2ecf20Sopenharmony_ci .driver = { 2468c2ecf20Sopenharmony_ci .name = MODULE_NAME, 2478c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(rpi_exp_gpio_ids), 2488c2ecf20Sopenharmony_ci }, 2498c2ecf20Sopenharmony_ci .probe = rpi_exp_gpio_probe, 2508c2ecf20Sopenharmony_ci}; 2518c2ecf20Sopenharmony_cimodule_platform_driver(rpi_exp_gpio_driver); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2548c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.org>"); 2558c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Raspberry Pi 3 expander GPIO driver"); 2568c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:rpi-exp-gpio"); 257