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