1 /*- 2 * Copyright (c) 2017 Ian Lepore <ian@freebsd.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 /* 27 * Support routines usable by any SoC sdhci bridge driver that uses gpio pins 28 * for card detect and write protect, and uses FDT data to describe those pins. 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include <sys/param.h> 35 #include <sys/bus.h> 36 #include <sys/gpio.h> 37 #include <sys/sysctl.h> 38 #include <sys/taskqueue.h> 39 40 #include <dev/gpio/gpiobusvar.h> 41 #include <dev/mmc/bridge.h> 42 #include <dev/ofw/ofw_bus.h> 43 #include <dev/ofw/ofw_bus_subr.h> 44 #include <dev/sdhci/sdhci.h> 45 #include <dev/sdhci/sdhci_fdt_gpio.h> 46 47 struct sdhci_fdt_gpio { 48 device_t dev; 49 struct sdhci_slot * slot; 50 gpio_pin_t wp_pin; 51 gpio_pin_t cd_pin; 52 void * cd_ihandler; 53 struct resource * cd_ires; 54 int cd_irid; 55 bool wp_disabled; 56 bool wp_inverted; 57 bool cd_disabled; 58 bool cd_inverted; 59 }; 60 61 /* 62 * Card detect interrupt handler. 63 */ 64 static void 65 cd_intr(void *arg) 66 { 67 struct sdhci_fdt_gpio *gpio = arg; 68 69 sdhci_handle_card_present(gpio->slot, sdhci_fdt_gpio_get_present(gpio)); 70 } 71 72 /* 73 * Card detect setup. 74 */ 75 static void 76 cd_setup(struct sdhci_fdt_gpio *gpio, phandle_t node) 77 { 78 int pincaps; 79 device_t dev; 80 const char *cd_mode_str; 81 82 dev = gpio->dev; 83 84 /* 85 * If the device is flagged as non-removable, set that slot option, and 86 * set a flag to make sdhci_fdt_gpio_get_present() always return true. 87 */ 88 if (OF_hasprop(node, "non-removable")) { 89 gpio->slot->opt |= SDHCI_NON_REMOVABLE; 90 gpio->cd_disabled = true; 91 if (bootverbose) 92 device_printf(dev, "Non-removable media"); 93 return; 94 } 95 96 /* 97 * If there is no cd-gpios property, then presumably the hardware 98 * PRESENT_STATE register and interrupts will reflect card state 99 * properly, and there's nothing more for us to do. Our get_present() 100 * will return sdhci_generic_get_card_present() because cd_pin is NULL. 101 * 102 * If there is a property, make sure we can read the pin. 103 */ 104 if (gpio_pin_get_by_ofw_property(dev, node, "cd-gpios", &gpio->cd_pin)) 105 return; 106 107 if (gpio_pin_getcaps(gpio->cd_pin, &pincaps) != 0 || 108 !(pincaps & GPIO_PIN_INPUT)) { 109 device_printf(dev, "Cannot read card-detect gpio pin; " 110 "setting card-always-present flag.\n"); 111 gpio->cd_disabled = true; 112 return; 113 } 114 115 if (OF_hasprop(node, "cd-inverted")) 116 gpio->cd_inverted = true; 117 118 /* 119 * If the pin can trigger an interrupt on both rising and falling edges, 120 * we can use it to detect card presence changes. If not, we'll request 121 * card presence polling instead of using interrupts. 122 */ 123 if (!(pincaps & GPIO_INTR_EDGE_BOTH)) { 124 if (bootverbose) 125 device_printf(dev, "Cannot configure " 126 "GPIO_INTR_EDGE_BOTH for card detect\n"); 127 goto without_interrupts; 128 } 129 130 /* 131 * Create an interrupt resource from the pin and set up the interrupt. 132 */ 133 if ((gpio->cd_ires = gpio_alloc_intr_resource(dev, &gpio->cd_irid, 134 RF_ACTIVE, gpio->cd_pin, GPIO_INTR_EDGE_BOTH)) == NULL) { 135 if (bootverbose) 136 device_printf(dev, "Cannot allocate an IRQ for card " 137 "detect GPIO\n"); 138 goto without_interrupts; 139 } 140 141 if (bus_setup_intr(dev, gpio->cd_ires, INTR_TYPE_BIO | INTR_MPSAFE, 142 NULL, cd_intr, gpio, &gpio->cd_ihandler) != 0) { 143 device_printf(dev, "Unable to setup card-detect irq handler\n"); 144 gpio->cd_ihandler = NULL; 145 goto without_interrupts; 146 } 147 148 without_interrupts: 149 150 /* 151 * If we have a readable gpio pin, but didn't successfully configure 152 * gpio interrupts, ask the sdhci driver to poll from a callout. 153 */ 154 if (gpio->cd_ihandler == NULL) { 155 cd_mode_str = "polling"; 156 gpio->slot->quirks |= SDHCI_QUIRK_POLL_CARD_PRESENT; 157 } else { 158 cd_mode_str = "interrupts"; 159 } 160 161 if (bootverbose) { 162 device_printf(dev, "Card presence detect on %s pin %u, " 163 "configured for %s.\n", 164 device_get_nameunit(gpio->cd_pin->dev), gpio->cd_pin->pin, 165 cd_mode_str); 166 } 167 } 168 169 /* 170 * Write protect setup. 171 */ 172 static void 173 wp_setup(struct sdhci_fdt_gpio *gpio, phandle_t node) 174 { 175 device_t dev; 176 177 dev = gpio->dev; 178 179 if (OF_hasprop(node, "wp-disable")) 180 return; 181 182 if (gpio_pin_get_by_ofw_property(dev, node, "wp-gpios", &gpio->wp_pin)) 183 return; 184 185 if (OF_hasprop(node, "wp-inverted")) 186 gpio->wp_inverted = true; 187 188 if (bootverbose) 189 device_printf(dev, "Write protect switch on %s pin %u\n", 190 device_get_nameunit(gpio->cd_pin->dev), gpio->cd_pin->pin); 191 } 192 193 struct sdhci_fdt_gpio * 194 sdhci_fdt_gpio_setup(device_t dev, struct sdhci_slot *slot) 195 { 196 phandle_t node; 197 struct sdhci_fdt_gpio *gpio; 198 199 gpio = malloc(sizeof(*gpio), M_DEVBUF, M_ZERO | M_WAITOK); 200 gpio->dev = dev; 201 gpio->slot = slot; 202 203 node = ofw_bus_get_node(dev); 204 205 wp_setup(gpio, node); 206 cd_setup(gpio, node); 207 208 return (gpio); 209 } 210 211 void 212 sdhci_fdt_gpio_teardown(struct sdhci_fdt_gpio *gpio) 213 { 214 215 if (gpio == NULL) 216 return; 217 218 if (gpio->cd_ihandler != NULL) { 219 bus_teardown_intr(gpio->dev, gpio->cd_ires, gpio->cd_ihandler); 220 } 221 222 free(gpio, M_DEVBUF); 223 } 224 225 bool 226 sdhci_fdt_gpio_get_present(struct sdhci_fdt_gpio *gpio) 227 { 228 bool pinstate; 229 230 if (gpio->cd_disabled) 231 return (true); 232 233 if (gpio->cd_pin == NULL) 234 return (sdhci_generic_get_card_present(gpio->slot->bus, 235 gpio->slot)); 236 237 gpio_pin_is_active(gpio->cd_pin, &pinstate); 238 239 return (pinstate ^ gpio->cd_inverted); 240 } 241 242 int 243 sdhci_fdt_gpio_get_readonly(struct sdhci_fdt_gpio *gpio) 244 { 245 bool pinstate; 246 247 if (gpio->wp_disabled) 248 return (false); 249 250 if (gpio->wp_pin == NULL) 251 return (sdhci_generic_get_ro(gpio->slot->bus, gpio->slot->dev)); 252 253 gpio_pin_is_active(gpio->wp_pin, &pinstate); 254 255 return (pinstate ^ gpio->wp_inverted); 256 } 257