1311734Sian/*- 2311734Sian * Copyright (c) 2017 Ian Lepore <ian@freebsd.org> 3311734Sian * All rights reserved. 4311734Sian * 5311734Sian * Redistribution and use in source and binary forms, with or without 6311734Sian * modification, are permitted provided that the following conditions 7311734Sian * are met: 8311734Sian * 1. Redistributions of source code must retain the above copyright 9311734Sian * notice, this list of conditions and the following disclaimer. 10311734Sian * 2. Redistributions in binary form must reproduce the above copyright 11311734Sian * notice, this list of conditions and the following disclaimer in the 12311734Sian * documentation and/or other materials provided with the distribution. 13311734Sian * 14311734Sian * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15311734Sian * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16311734Sian * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17311734Sian * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18311734Sian * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19311734Sian * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20311734Sian * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21311734Sian * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22311734Sian * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23311734Sian * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24311734Sian */ 25311734Sian 26311734Sian/* 27311734Sian * Support routines usable by any SoC sdhci bridge driver that uses gpio pins 28311734Sian * for card detect and write protect, and uses FDT data to describe those pins. 29311734Sian */ 30311734Sian 31311734Sian#include <sys/cdefs.h> 32311734Sian__FBSDID("$FreeBSD: stable/11/sys/dev/sdhci/sdhci_fdt_gpio.c 325023 2017-10-26 22:19:28Z ian $"); 33311734Sian 34311734Sian#include <sys/param.h> 35311734Sian#include <sys/bus.h> 36311734Sian#include <sys/gpio.h> 37311734Sian#include <sys/sysctl.h> 38314508Sian#include <sys/systm.h> 39311734Sian#include <sys/taskqueue.h> 40311734Sian 41311734Sian#include <dev/gpio/gpiobusvar.h> 42311734Sian#include <dev/mmc/bridge.h> 43311734Sian#include <dev/ofw/ofw_bus.h> 44311734Sian#include <dev/ofw/ofw_bus_subr.h> 45311734Sian#include <dev/sdhci/sdhci.h> 46311734Sian#include <dev/sdhci/sdhci_fdt_gpio.h> 47311734Sian 48311734Sianstruct sdhci_fdt_gpio { 49311734Sian device_t dev; 50311734Sian struct sdhci_slot * slot; 51311734Sian gpio_pin_t wp_pin; 52311734Sian gpio_pin_t cd_pin; 53311734Sian void * cd_ihandler; 54311734Sian struct resource * cd_ires; 55311734Sian int cd_irid; 56311734Sian bool wp_disabled; 57311734Sian bool wp_inverted; 58311734Sian bool cd_disabled; 59311734Sian bool cd_inverted; 60311734Sian}; 61311734Sian 62311734Sian/* 63311734Sian * Card detect interrupt handler. 64311734Sian */ 65311734Sianstatic void 66311734Siancd_intr(void *arg) 67311734Sian{ 68311734Sian struct sdhci_fdt_gpio *gpio = arg; 69311734Sian 70311734Sian sdhci_handle_card_present(gpio->slot, sdhci_fdt_gpio_get_present(gpio)); 71311734Sian} 72311734Sian 73311734Sian/* 74311734Sian * Card detect setup. 75311734Sian */ 76311734Sianstatic void 77311734Siancd_setup(struct sdhci_fdt_gpio *gpio, phandle_t node) 78311734Sian{ 79311734Sian int pincaps; 80311734Sian device_t dev; 81311734Sian const char *cd_mode_str; 82311734Sian 83311734Sian dev = gpio->dev; 84311734Sian 85311734Sian /* 86311734Sian * If the device is flagged as non-removable, set that slot option, and 87311734Sian * set a flag to make sdhci_fdt_gpio_get_present() always return true. 88311734Sian */ 89311734Sian if (OF_hasprop(node, "non-removable")) { 90311734Sian gpio->slot->opt |= SDHCI_NON_REMOVABLE; 91311734Sian gpio->cd_disabled = true; 92311734Sian if (bootverbose) 93318791Sloos device_printf(dev, "Non-removable media\n"); 94311734Sian return; 95311734Sian } 96311734Sian 97311734Sian /* 98311734Sian * If there is no cd-gpios property, then presumably the hardware 99311734Sian * PRESENT_STATE register and interrupts will reflect card state 100311734Sian * properly, and there's nothing more for us to do. Our get_present() 101311734Sian * will return sdhci_generic_get_card_present() because cd_pin is NULL. 102311734Sian * 103311734Sian * If there is a property, make sure we can read the pin. 104311734Sian */ 105311734Sian if (gpio_pin_get_by_ofw_property(dev, node, "cd-gpios", &gpio->cd_pin)) 106311734Sian return; 107311734Sian 108311734Sian if (gpio_pin_getcaps(gpio->cd_pin, &pincaps) != 0 || 109311734Sian !(pincaps & GPIO_PIN_INPUT)) { 110311734Sian device_printf(dev, "Cannot read card-detect gpio pin; " 111311734Sian "setting card-always-present flag.\n"); 112311734Sian gpio->cd_disabled = true; 113311734Sian return; 114311734Sian } 115311734Sian 116311734Sian if (OF_hasprop(node, "cd-inverted")) 117311734Sian gpio->cd_inverted = true; 118311734Sian 119311734Sian /* 120311734Sian * If the pin can trigger an interrupt on both rising and falling edges, 121311734Sian * we can use it to detect card presence changes. If not, we'll request 122311734Sian * card presence polling instead of using interrupts. 123311734Sian */ 124311734Sian if (!(pincaps & GPIO_INTR_EDGE_BOTH)) { 125311734Sian if (bootverbose) 126311734Sian device_printf(dev, "Cannot configure " 127311734Sian "GPIO_INTR_EDGE_BOTH for card detect\n"); 128311734Sian goto without_interrupts; 129311734Sian } 130311734Sian 131311734Sian /* 132311734Sian * Create an interrupt resource from the pin and set up the interrupt. 133311734Sian */ 134311734Sian if ((gpio->cd_ires = gpio_alloc_intr_resource(dev, &gpio->cd_irid, 135311734Sian RF_ACTIVE, gpio->cd_pin, GPIO_INTR_EDGE_BOTH)) == NULL) { 136311734Sian if (bootverbose) 137311734Sian device_printf(dev, "Cannot allocate an IRQ for card " 138311734Sian "detect GPIO\n"); 139311734Sian goto without_interrupts; 140311734Sian } 141311734Sian 142311734Sian if (bus_setup_intr(dev, gpio->cd_ires, INTR_TYPE_BIO | INTR_MPSAFE, 143311734Sian NULL, cd_intr, gpio, &gpio->cd_ihandler) != 0) { 144311734Sian device_printf(dev, "Unable to setup card-detect irq handler\n"); 145311734Sian gpio->cd_ihandler = NULL; 146311734Sian goto without_interrupts; 147311734Sian } 148311734Sian 149311734Sianwithout_interrupts: 150311734Sian 151311734Sian /* 152311734Sian * If we have a readable gpio pin, but didn't successfully configure 153311734Sian * gpio interrupts, ask the sdhci driver to poll from a callout. 154311734Sian */ 155311734Sian if (gpio->cd_ihandler == NULL) { 156311734Sian cd_mode_str = "polling"; 157311734Sian gpio->slot->quirks |= SDHCI_QUIRK_POLL_CARD_PRESENT; 158311734Sian } else { 159311734Sian cd_mode_str = "interrupts"; 160311734Sian } 161311734Sian 162311734Sian if (bootverbose) { 163311734Sian device_printf(dev, "Card presence detect on %s pin %u, " 164311734Sian "configured for %s.\n", 165311734Sian device_get_nameunit(gpio->cd_pin->dev), gpio->cd_pin->pin, 166311734Sian cd_mode_str); 167311734Sian } 168311734Sian} 169311734Sian 170311734Sian/* 171311734Sian * Write protect setup. 172311734Sian */ 173311734Sianstatic void 174311734Sianwp_setup(struct sdhci_fdt_gpio *gpio, phandle_t node) 175311734Sian{ 176311734Sian device_t dev; 177311734Sian 178311734Sian dev = gpio->dev; 179311734Sian 180318791Sloos if (OF_hasprop(node, "wp-disable")) { 181318791Sloos gpio->wp_disabled = true; 182318791Sloos if (bootverbose) 183318791Sloos device_printf(dev, "Write protect disabled\n"); 184311734Sian return; 185318791Sloos } 186311734Sian 187311734Sian if (gpio_pin_get_by_ofw_property(dev, node, "wp-gpios", &gpio->wp_pin)) 188311734Sian return; 189311734Sian 190311734Sian if (OF_hasprop(node, "wp-inverted")) 191311734Sian gpio->wp_inverted = true; 192311734Sian 193311734Sian if (bootverbose) 194311734Sian device_printf(dev, "Write protect switch on %s pin %u\n", 195314514Sian device_get_nameunit(gpio->wp_pin->dev), gpio->wp_pin->pin); 196311734Sian} 197311734Sian 198311734Sianstruct sdhci_fdt_gpio * 199311734Siansdhci_fdt_gpio_setup(device_t dev, struct sdhci_slot *slot) 200311734Sian{ 201311734Sian phandle_t node; 202311734Sian struct sdhci_fdt_gpio *gpio; 203311734Sian 204311734Sian gpio = malloc(sizeof(*gpio), M_DEVBUF, M_ZERO | M_WAITOK); 205311734Sian gpio->dev = dev; 206311734Sian gpio->slot = slot; 207311734Sian 208311734Sian node = ofw_bus_get_node(dev); 209311734Sian 210311734Sian wp_setup(gpio, node); 211311734Sian cd_setup(gpio, node); 212311734Sian 213311734Sian return (gpio); 214311734Sian} 215311734Sian 216311734Sianvoid 217311734Siansdhci_fdt_gpio_teardown(struct sdhci_fdt_gpio *gpio) 218311734Sian{ 219311734Sian 220311734Sian if (gpio == NULL) 221311734Sian return; 222311734Sian 223325023Sian if (gpio->cd_ihandler != NULL) 224311734Sian bus_teardown_intr(gpio->dev, gpio->cd_ires, gpio->cd_ihandler); 225325023Sian if (gpio->wp_pin != NULL) 226325023Sian gpio_pin_release(gpio->wp_pin); 227325023Sian if (gpio->cd_pin != NULL) 228325023Sian gpio_pin_release(gpio->cd_pin); 229325023Sian if (gpio->cd_ires != NULL) 230325023Sian bus_release_resource(gpio->dev, SYS_RES_IRQ, 0, gpio->cd_ires); 231311734Sian 232311734Sian free(gpio, M_DEVBUF); 233311734Sian} 234311734Sian 235311734Sianbool 236311734Siansdhci_fdt_gpio_get_present(struct sdhci_fdt_gpio *gpio) 237311734Sian{ 238311734Sian bool pinstate; 239311734Sian 240311734Sian if (gpio->cd_disabled) 241311734Sian return (true); 242311734Sian 243311734Sian if (gpio->cd_pin == NULL) 244311734Sian return (sdhci_generic_get_card_present(gpio->slot->bus, 245311734Sian gpio->slot)); 246311734Sian 247311734Sian gpio_pin_is_active(gpio->cd_pin, &pinstate); 248311734Sian 249311734Sian return (pinstate ^ gpio->cd_inverted); 250311734Sian} 251311734Sian 252311734Sianint 253311734Siansdhci_fdt_gpio_get_readonly(struct sdhci_fdt_gpio *gpio) 254311734Sian{ 255311734Sian bool pinstate; 256311734Sian 257311734Sian if (gpio->wp_disabled) 258311734Sian return (false); 259311734Sian 260311734Sian if (gpio->wp_pin == NULL) 261311734Sian return (sdhci_generic_get_ro(gpio->slot->bus, gpio->slot->dev)); 262311734Sian 263311734Sian gpio_pin_is_active(gpio->wp_pin, &pinstate); 264311734Sian 265311734Sian return (pinstate ^ gpio->wp_inverted); 266311734Sian} 267