xref: /freebsd/sys/dev/mmc/mmc_pwrseq.c (revision ae7e8a02e6e93455e026036132c4d053b2c12ad9)
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