|
| 1 | +/* |
| 2 | + * Broadcom expander GPIO driver |
| 3 | + * |
| 4 | + * Uses the firmware mailbox service to communicate with the |
| 5 | + * GPIO expander on the VPU. |
| 6 | + * |
| 7 | + * Copyright (C) 2017 Raspberry Pi Trading Ltd. |
| 8 | + * |
| 9 | + * Author: Dave Stevenson <[email protected]> |
| 10 | + * Based on gpio-bcm-virt.c by Dom Cobley <[email protected]> |
| 11 | + * |
| 12 | + * This program is free software; you can redistribute it and/or modify |
| 13 | + * it under the terms of the GNU General Public License as published by |
| 14 | + * the Free Software Foundation; either version 2 of the License, or |
| 15 | + * (at your option) any later version. |
| 16 | + */ |
| 17 | + |
| 18 | +#include <linux/err.h> |
| 19 | +#include <linux/gpio.h> |
| 20 | +#include <linux/module.h> |
| 21 | +#include <linux/platform_device.h> |
| 22 | +#include <linux/dma-mapping.h> |
| 23 | +#include <soc/bcm2835/raspberrypi-firmware.h> |
| 24 | + |
| 25 | +#define MODULE_NAME "brcmexp-gpio" |
| 26 | +#define NUM_GPIO 8 |
| 27 | + |
| 28 | +struct brcmexp_gpio { |
| 29 | + struct gpio_chip gc; |
| 30 | + struct device *dev; |
| 31 | + struct rpi_firmware *fw; |
| 32 | +}; |
| 33 | + |
| 34 | +struct gpio_set_config { |
| 35 | + u32 gpio, direction, polarity, term_en, term_pull_up, state; |
| 36 | +}; |
| 37 | + |
| 38 | +struct gpio_get_config { |
| 39 | + u32 gpio, direction, polarity, term_en, term_pull_up; |
| 40 | +}; |
| 41 | + |
| 42 | +struct gpio_get_set_state { |
| 43 | + u32 gpio, state; |
| 44 | +}; |
| 45 | + |
| 46 | +static int brcmexp_gpio_get_polarity(struct gpio_chip *gc, unsigned int off) |
| 47 | +{ |
| 48 | + struct brcmexp_gpio *gpio; |
| 49 | + struct gpio_get_config get; |
| 50 | + int ret; |
| 51 | + |
| 52 | + gpio = container_of(gc, struct brcmexp_gpio, gc); |
| 53 | + |
| 54 | + get.gpio = off + gpio->gc.base; /* GPIO to update */ |
| 55 | + |
| 56 | + ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_CONFIG, |
| 57 | + &get, sizeof(get)); |
| 58 | + if (ret) { |
| 59 | + dev_err(gpio->dev, |
| 60 | + "Failed to get GPIO %u config (%d)\n", off, ret); |
| 61 | + return ret; |
| 62 | + } |
| 63 | + return get.polarity; |
| 64 | +} |
| 65 | + |
| 66 | +static int brcmexp_gpio_dir_in(struct gpio_chip *gc, unsigned int off) |
| 67 | +{ |
| 68 | + struct brcmexp_gpio *gpio; |
| 69 | + struct gpio_set_config set_in; |
| 70 | + int ret; |
| 71 | + |
| 72 | + gpio = container_of(gc, struct brcmexp_gpio, gc); |
| 73 | + |
| 74 | + set_in.gpio = off + gpio->gc.base; /* GPIO to update */ |
| 75 | + set_in.direction = 0; /* Input */ |
| 76 | + set_in.polarity = brcmexp_gpio_get_polarity(gc, off); |
| 77 | + /* Retain existing setting */ |
| 78 | + set_in.term_en = 0; /* termination disabled */ |
| 79 | + set_in.term_pull_up = 0; /* n/a as termination disabled */ |
| 80 | + set_in.state = 0; /* n/a as configured as an input */ |
| 81 | + |
| 82 | + ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_CONFIG, |
| 83 | + &set_in, sizeof(set_in)); |
| 84 | + if (ret) { |
| 85 | + dev_err(gpio->dev, |
| 86 | + "Failed to set GPIO %u to input (%d)\n", |
| 87 | + off, ret); |
| 88 | + return ret; |
| 89 | + } |
| 90 | + return 0; |
| 91 | +} |
| 92 | + |
| 93 | +static int brcmexp_gpio_dir_out(struct gpio_chip *gc, unsigned int off, int val) |
| 94 | +{ |
| 95 | + struct brcmexp_gpio *gpio; |
| 96 | + struct gpio_set_config set_out; |
| 97 | + int ret; |
| 98 | + |
| 99 | + gpio = container_of(gc, struct brcmexp_gpio, gc); |
| 100 | + |
| 101 | + set_out.gpio = off + gpio->gc.base; /* GPIO to update */ |
| 102 | + set_out.direction = 1; /* Output */ |
| 103 | + set_out.polarity = brcmexp_gpio_get_polarity(gc, off); |
| 104 | + /* Retain existing setting */ |
| 105 | + set_out.term_en = 0; /* n/a as an output */ |
| 106 | + set_out.term_pull_up = 0; /* n/a as termination disabled */ |
| 107 | + set_out.state = val; /* Output state */ |
| 108 | + |
| 109 | + ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_CONFIG, |
| 110 | + &set_out, sizeof(set_out)); |
| 111 | + if (ret) { |
| 112 | + dev_err(gpio->dev, |
| 113 | + "Failed to set GPIO %u to output (%d)\n", off, ret); |
| 114 | + return ret; |
| 115 | + } |
| 116 | + return 0; |
| 117 | +} |
| 118 | + |
| 119 | +static int brcmexp_gpio_get_direction(struct gpio_chip *gc, unsigned int off) |
| 120 | +{ |
| 121 | + struct brcmexp_gpio *gpio; |
| 122 | + struct gpio_get_config get; |
| 123 | + int ret; |
| 124 | + |
| 125 | + gpio = container_of(gc, struct brcmexp_gpio, gc); |
| 126 | + |
| 127 | + get.gpio = off + gpio->gc.base; /* GPIO to update */ |
| 128 | + |
| 129 | + ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_CONFIG, |
| 130 | + &get, sizeof(get)); |
| 131 | + if (ret) { |
| 132 | + dev_err(gpio->dev, |
| 133 | + "Failed to get GPIO %u config (%d)\n", off, ret); |
| 134 | + return ret; |
| 135 | + } |
| 136 | + return get.direction ? GPIOF_DIR_OUT : GPIOF_DIR_IN; |
| 137 | +} |
| 138 | + |
| 139 | +static int brcmexp_gpio_get(struct gpio_chip *gc, unsigned int off) |
| 140 | +{ |
| 141 | + struct brcmexp_gpio *gpio; |
| 142 | + struct gpio_get_set_state get; |
| 143 | + int ret; |
| 144 | + |
| 145 | + gpio = container_of(gc, struct brcmexp_gpio, gc); |
| 146 | + |
| 147 | + get.gpio = off + gpio->gc.base; /* GPIO to update */ |
| 148 | + get.state = 0; /* storage for returned value */ |
| 149 | + |
| 150 | + ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_STATE, |
| 151 | + &get, sizeof(get)); |
| 152 | + if (ret) { |
| 153 | + dev_err(gpio->dev, |
| 154 | + "Failed to get GPIO %u state (%d)\n", off, ret); |
| 155 | + return ret; |
| 156 | + } |
| 157 | + return !!get.state; |
| 158 | +} |
| 159 | + |
| 160 | +static void brcmexp_gpio_set(struct gpio_chip *gc, unsigned int off, int val) |
| 161 | +{ |
| 162 | + struct brcmexp_gpio *gpio; |
| 163 | + struct gpio_get_set_state set; |
| 164 | + int ret; |
| 165 | + |
| 166 | + gpio = container_of(gc, struct brcmexp_gpio, gc); |
| 167 | + |
| 168 | + off += gpio->gc.base; |
| 169 | + |
| 170 | + set.gpio = off + gpio->gc.base; /* GPIO to update */ |
| 171 | + set.state = val; /* Output state */ |
| 172 | + |
| 173 | + ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_STATE, |
| 174 | + &set, sizeof(set)); |
| 175 | + if (ret) |
| 176 | + dev_err(gpio->dev, |
| 177 | + "Failed to set GPIO %u state (%d)\n", off, ret); |
| 178 | +} |
| 179 | + |
| 180 | +static int brcmexp_gpio_probe(struct platform_device *pdev) |
| 181 | +{ |
| 182 | + int err = 0; |
| 183 | + struct device *dev = &pdev->dev; |
| 184 | + struct device_node *np = dev->of_node; |
| 185 | + struct device_node *fw_node; |
| 186 | + struct rpi_firmware *fw; |
| 187 | + struct brcmexp_gpio *ucb; |
| 188 | + |
| 189 | + fw_node = of_parse_phandle(np, "firmware", 0); |
| 190 | + if (!fw_node) { |
| 191 | + dev_err(dev, "Missing firmware node\n"); |
| 192 | + return -ENOENT; |
| 193 | + } |
| 194 | + |
| 195 | + fw = rpi_firmware_get(fw_node); |
| 196 | + if (!fw) |
| 197 | + return -EPROBE_DEFER; |
| 198 | + |
| 199 | + ucb = devm_kzalloc(dev, sizeof(*ucb), GFP_KERNEL); |
| 200 | + if (!ucb) |
| 201 | + return -EINVAL; |
| 202 | + |
| 203 | + ucb->fw = fw; |
| 204 | + ucb->dev = dev; |
| 205 | + ucb->gc.label = MODULE_NAME; |
| 206 | + ucb->gc.owner = THIS_MODULE; |
| 207 | + ucb->gc.of_node = np; |
| 208 | + ucb->gc.base = 128; |
| 209 | + ucb->gc.ngpio = NUM_GPIO; |
| 210 | + |
| 211 | + ucb->gc.direction_input = brcmexp_gpio_dir_in; |
| 212 | + ucb->gc.direction_output = brcmexp_gpio_dir_out; |
| 213 | + ucb->gc.get_direction = brcmexp_gpio_get_direction; |
| 214 | + ucb->gc.get = brcmexp_gpio_get; |
| 215 | + ucb->gc.set = brcmexp_gpio_set; |
| 216 | + ucb->gc.can_sleep = true; |
| 217 | + |
| 218 | + err = gpiochip_add(&ucb->gc); |
| 219 | + if (err) |
| 220 | + return err; |
| 221 | + |
| 222 | + platform_set_drvdata(pdev, ucb); |
| 223 | + |
| 224 | + return 0; |
| 225 | +} |
| 226 | + |
| 227 | +static int brcmexp_gpio_remove(struct platform_device *pdev) |
| 228 | +{ |
| 229 | + struct brcmexp_gpio *ucb = platform_get_drvdata(pdev); |
| 230 | + |
| 231 | + gpiochip_remove(&ucb->gc); |
| 232 | + |
| 233 | + return 0; |
| 234 | +} |
| 235 | + |
| 236 | +static const struct of_device_id __maybe_unused brcmexp_gpio_ids[] = { |
| 237 | + { .compatible = "brcm,bcm2835-expgpio" }, |
| 238 | + { } |
| 239 | +}; |
| 240 | +MODULE_DEVICE_TABLE(of, brcmexp_gpio_ids); |
| 241 | + |
| 242 | +static struct platform_driver brcmexp_gpio_driver = { |
| 243 | + .driver = { |
| 244 | + .name = MODULE_NAME, |
| 245 | + .owner = THIS_MODULE, |
| 246 | + .of_match_table = of_match_ptr(brcmexp_gpio_ids), |
| 247 | + }, |
| 248 | + .probe = brcmexp_gpio_probe, |
| 249 | + .remove = brcmexp_gpio_remove, |
| 250 | +}; |
| 251 | +module_platform_driver(brcmexp_gpio_driver); |
| 252 | + |
| 253 | +MODULE_LICENSE("GPL"); |
| 254 | +MODULE_AUTHOR( "Dave Stevenson <[email protected]>"); |
| 255 | +MODULE_DESCRIPTION("brcm-exp GPIO driver"); |
| 256 | +MODULE_ALIAS("platform:brcmexp-gpio"); |
0 commit comments