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