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 #include <sys/param.h> 33 #include <sys/bus.h> 34 #include <sys/gpio.h> 35 #include <sys/sysctl.h> 36 #include <sys/systm.h> 37 #include <sys/taskqueue.h> 38 39 #include <dev/gpio/gpiobusvar.h> 40 #include <dev/mmc/bridge.h> 41 #include <dev/ofw/ofw_bus.h> 42 #include <dev/ofw/ofw_bus_subr.h> 43 #include <dev/sdhci/sdhci.h> 44 #include <dev/sdhci/sdhci_fdt_gpio.h> 45 46 struct sdhci_fdt_gpio { 47 device_t dev; 48 struct sdhci_slot * slot; 49 gpio_pin_t wp_pin; 50 gpio_pin_t cd_pin; 51 void * cd_ihandler; 52 struct resource * cd_ires; 53 int cd_irid; 54 bool wp_disabled; 55 bool wp_inverted; 56 bool cd_disabled; 57 bool cd_inverted; 58 }; 59 60 /* 61 * Card detect interrupt handler. 62 */ 63 static void 64 cd_intr(void *arg) 65 { 66 struct sdhci_fdt_gpio *gpio = arg; 67 68 sdhci_handle_card_present(gpio->slot, sdhci_fdt_gpio_get_present(gpio)); 69 } 70 71 /* 72 * Card detect setup. 73 */ 74 static void 75 cd_setup(struct sdhci_fdt_gpio *gpio, phandle_t node) 76 { 77 int pincaps; 78 device_t dev; 79 const char *cd_mode_str; 80 81 dev = gpio->dev; 82 83 /* 84 * If the device is flagged as non-removable, set that slot option, and 85 * set a flag to make sdhci_fdt_gpio_get_present() always return true. 86 */ 87 if (OF_hasprop(node, "non-removable")) { 88 gpio->slot->opt |= SDHCI_NON_REMOVABLE; 89 gpio->cd_disabled = true; 90 if (bootverbose) 91 device_printf(dev, "Non-removable media\n"); 92 return; 93 } 94 95 /* 96 * If there is no cd-gpios property, then presumably the hardware 97 * PRESENT_STATE register and interrupts will reflect card state 98 * properly, and there's nothing more for us to do. Our get_present() 99 * will return sdhci_generic_get_card_present() because cd_pin is NULL. 100 * 101 * If there is a property, make sure we can read the pin. 102 */ 103 if (gpio_pin_get_by_ofw_property(dev, node, "cd-gpios", &gpio->cd_pin)) 104 return; 105 106 if (gpio_pin_getcaps(gpio->cd_pin, &pincaps) != 0 || 107 !(pincaps & GPIO_PIN_INPUT)) { 108 device_printf(dev, "Cannot read card-detect gpio pin; " 109 "setting card-always-present flag.\n"); 110 gpio->cd_disabled = true; 111 return; 112 } 113 114 if (OF_hasprop(node, "cd-inverted")) 115 gpio->cd_inverted = true; 116 117 /* 118 * If the pin can trigger an interrupt on both rising and falling edges, 119 * we can use it to detect card presence changes. If not, we'll request 120 * card presence polling instead of using interrupts. 121 */ 122 if (!(pincaps & GPIO_INTR_EDGE_BOTH)) { 123 if (bootverbose) 124 device_printf(dev, "Cannot configure " 125 "GPIO_INTR_EDGE_BOTH for card detect\n"); 126 goto without_interrupts; 127 } 128 129 /* 130 * Create an interrupt resource from the pin and set up the interrupt. 131 */ 132 if ((gpio->cd_ires = gpio_alloc_intr_resource(dev, &gpio->cd_irid, 133 RF_ACTIVE, gpio->cd_pin, GPIO_INTR_EDGE_BOTH)) == NULL) { 134 if (bootverbose) 135 device_printf(dev, "Cannot allocate an IRQ for card " 136 "detect GPIO\n"); 137 goto without_interrupts; 138 } 139 140 if (bus_setup_intr(dev, gpio->cd_ires, INTR_TYPE_BIO | INTR_MPSAFE, 141 NULL, cd_intr, gpio, &gpio->cd_ihandler) != 0) { 142 device_printf(dev, "Unable to setup card-detect irq handler\n"); 143 gpio->cd_ihandler = NULL; 144 goto without_interrupts; 145 } 146 147 without_interrupts: 148 149 /* 150 * If we have a readable gpio pin, but didn't successfully configure 151 * gpio interrupts, ask the sdhci driver to poll from a callout. 152 */ 153 if (gpio->cd_ihandler == NULL) { 154 cd_mode_str = "polling"; 155 gpio->slot->quirks |= SDHCI_QUIRK_POLL_CARD_PRESENT; 156 } else { 157 cd_mode_str = "interrupts"; 158 } 159 160 if (bootverbose) { 161 device_printf(dev, "Card presence detect on %s pin %u, " 162 "configured for %s.\n", 163 device_get_nameunit(gpio->cd_pin->dev), gpio->cd_pin->pin, 164 cd_mode_str); 165 } 166 } 167 168 /* 169 * Write protect setup. 170 */ 171 static void 172 wp_setup(struct sdhci_fdt_gpio *gpio, phandle_t node) 173 { 174 device_t dev; 175 176 dev = gpio->dev; 177 178 if (OF_hasprop(node, "wp-disable")) { 179 gpio->wp_disabled = true; 180 if (bootverbose) 181 device_printf(dev, "Write protect disabled\n"); 182 return; 183 } 184 185 if (gpio_pin_get_by_ofw_property(dev, node, "wp-gpios", &gpio->wp_pin)) 186 return; 187 188 if (OF_hasprop(node, "wp-inverted")) 189 gpio->wp_inverted = true; 190 191 if (bootverbose) 192 device_printf(dev, "Write protect switch on %s pin %u\n", 193 device_get_nameunit(gpio->wp_pin->dev), gpio->wp_pin->pin); 194 } 195 196 struct sdhci_fdt_gpio * 197 sdhci_fdt_gpio_setup(device_t dev, struct sdhci_slot *slot) 198 { 199 phandle_t node; 200 struct sdhci_fdt_gpio *gpio; 201 202 gpio = malloc(sizeof(*gpio), M_DEVBUF, M_ZERO | M_WAITOK); 203 gpio->dev = dev; 204 gpio->slot = slot; 205 206 node = ofw_bus_get_node(dev); 207 208 wp_setup(gpio, node); 209 cd_setup(gpio, node); 210 211 return (gpio); 212 } 213 214 void 215 sdhci_fdt_gpio_teardown(struct sdhci_fdt_gpio *gpio) 216 { 217 218 if (gpio == NULL) 219 return; 220 221 if (gpio->cd_ihandler != NULL) 222 bus_teardown_intr(gpio->dev, gpio->cd_ires, gpio->cd_ihandler); 223 if (gpio->wp_pin != NULL) 224 gpio_pin_release(gpio->wp_pin); 225 if (gpio->cd_pin != NULL) 226 gpio_pin_release(gpio->cd_pin); 227 if (gpio->cd_ires != NULL) 228 bus_release_resource(gpio->dev, SYS_RES_IRQ, 0, gpio->cd_ires); 229 230 free(gpio, M_DEVBUF); 231 } 232 233 bool 234 sdhci_fdt_gpio_get_present(struct sdhci_fdt_gpio *gpio) 235 { 236 bool pinstate; 237 238 if (gpio->cd_disabled) 239 return (true); 240 241 if (gpio->cd_pin == NULL) 242 return (sdhci_generic_get_card_present(gpio->slot->bus, 243 gpio->slot)); 244 245 gpio_pin_is_active(gpio->cd_pin, &pinstate); 246 247 return (pinstate ^ gpio->cd_inverted); 248 } 249 250 int 251 sdhci_fdt_gpio_get_readonly(struct sdhci_fdt_gpio *gpio) 252 { 253 bool pinstate; 254 255 if (gpio->wp_disabled) 256 return (false); 257 258 if (gpio->wp_pin == NULL) 259 return (sdhci_generic_get_ro(gpio->slot->bus, gpio->slot->dev)); 260 261 gpio_pin_is_active(gpio->wp_pin, &pinstate); 262 263 return (pinstate ^ gpio->wp_inverted); 264 } 265