1 /* 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright 2021 Emmanuel Vadot <manu@freebsd.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are 8 * met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 23 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 25 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 26 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 #include <sys/param.h> 33 #include <sys/bus.h> 34 #include <sys/kernel.h> 35 #include <sys/module.h> 36 #include <sys/gpio.h> 37 38 #include <dev/gpio/gpiobusvar.h> 39 #include <dev/ofw/ofw_bus.h> 40 #include <dev/ofw/ofw_bus_subr.h> 41 42 #include <dev/extres/clk/clk.h> 43 44 #include "mmc_pwrseq_if.h" 45 46 enum pwrseq_type { 47 PWRSEQ_SIMPLE = 1, 48 PWRSEQ_EMMC, 49 }; 50 51 static struct ofw_compat_data compat_data[] = { 52 { "mmc-pwrseq-simple", PWRSEQ_SIMPLE }, 53 { "mmc-pwrseq-emmc", PWRSEQ_EMMC }, 54 { NULL, 0 } 55 }; 56 57 struct mmc_pwrseq_softc { 58 enum pwrseq_type type; 59 clk_t ext_clock; 60 struct gpiobus_pin *reset_gpio; 61 62 uint32_t post_power_on_delay_ms; 63 uint32_t power_off_delay_us; 64 }; 65 66 static int 67 mmc_pwrseq_probe(device_t dev) 68 { 69 enum pwrseq_type type; 70 71 if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) 72 return (ENXIO); 73 74 type = (enum pwrseq_type)ofw_bus_search_compatible(dev, compat_data)->ocd_data; 75 switch (type) { 76 case PWRSEQ_SIMPLE: 77 device_set_desc(dev, "MMC Simple Power sequence"); 78 break; 79 case PWRSEQ_EMMC: 80 device_set_desc(dev, "MMC eMMC Power sequence"); 81 break; 82 } 83 return (BUS_PROBE_DEFAULT); 84 } 85 86 static int 87 mmc_pwrseq_attach(device_t dev) 88 { 89 struct mmc_pwrseq_softc *sc; 90 phandle_t node; 91 int rv; 92 93 sc = device_get_softc(dev); 94 sc->type = (enum pwrseq_type)ofw_bus_search_compatible(dev, compat_data)->ocd_data; 95 node = ofw_bus_get_node(dev); 96 97 if (sc->type == PWRSEQ_SIMPLE) { 98 if (OF_hasprop(node, "clocks")) { 99 rv = clk_get_by_ofw_name(dev, 0, "ext_clock", &sc->ext_clock); 100 if (rv != 0) { 101 device_printf(dev, 102 "Node have a clocks property but no clocks named \"ext_clock\"\n"); 103 return (ENXIO); 104 } 105 } 106 OF_getencprop(node, "post-power-on-delay-ms", &sc->post_power_on_delay_ms, sizeof(uint32_t)); 107 OF_getencprop(node, "power-off-delay-us", &sc->power_off_delay_us, sizeof(uint32_t)); 108 } 109 110 if (OF_hasprop(node, "reset-gpios")) { 111 if (gpio_pin_get_by_ofw_property(dev, node, "reset-gpios", 112 &sc->reset_gpio) != 0) { 113 device_printf(dev, "Cannot get the reset-gpios\n"); 114 return (ENXIO); 115 } 116 gpio_pin_setflags(sc->reset_gpio, GPIO_PIN_OUTPUT); 117 gpio_pin_set_active(sc->reset_gpio, true); 118 } 119 120 OF_device_register_xref(OF_xref_from_node(node), dev); 121 return (0); 122 } 123 124 static int 125 mmc_pwrseq_detach(device_t dev) 126 { 127 128 return (EBUSY); 129 } 130 131 static int 132 mmv_pwrseq_set_power(device_t dev, bool power_on) 133 { 134 struct mmc_pwrseq_softc *sc; 135 int rv; 136 137 sc = device_get_softc(dev); 138 139 if (power_on) { 140 if (sc->ext_clock) { 141 rv = clk_enable(sc->ext_clock); 142 if (rv != 0) 143 return (rv); 144 } 145 146 if (sc->reset_gpio) { 147 rv = gpio_pin_set_active(sc->reset_gpio, false); 148 if (rv != 0) 149 return (rv); 150 } 151 152 if (sc->post_power_on_delay_ms) 153 DELAY(sc->post_power_on_delay_ms * 1000); 154 } else { 155 if (sc->reset_gpio) { 156 rv = gpio_pin_set_active(sc->reset_gpio, true); 157 if (rv != 0) 158 return (rv); 159 } 160 161 if (sc->ext_clock) { 162 rv = clk_stop(sc->ext_clock); 163 if (rv != 0) 164 return (rv); 165 } 166 if (sc->power_off_delay_us) 167 DELAY(sc->power_off_delay_us); 168 } 169 170 return (0); 171 } 172 173 static device_method_t mmc_pwrseq_methods[] = { 174 /* Device interface */ 175 DEVMETHOD(device_probe, mmc_pwrseq_probe), 176 DEVMETHOD(device_attach, mmc_pwrseq_attach), 177 DEVMETHOD(device_detach, mmc_pwrseq_detach), 178 179 DEVMETHOD(mmc_pwrseq_set_power, mmv_pwrseq_set_power), 180 DEVMETHOD_END 181 }; 182 183 static driver_t mmc_pwrseq_driver = { 184 "mmc_pwrseq", 185 mmc_pwrseq_methods, 186 sizeof(struct mmc_pwrseq_softc), 187 }; 188 189 static devclass_t mmc_pwrseq_devclass; 190 191 EARLY_DRIVER_MODULE(mmc_pwrseq, simplebus, mmc_pwrseq_driver, mmc_pwrseq_devclass, 0, 0, 192 BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_FIRST); 193 MODULE_VERSION(mmc_pwrseq, 1); 194 SIMPLEBUS_PNP_INFO(compat_data); 195