xref: /freebsd/sys/dev/mmc/mmc_pwrseq.c (revision 783d3ff6d7fae619db8a7990b8a6387de0c677b5)
1 /*
2  * SPDX-License-Identifier: BSD-2-Clause
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/param.h>
30 #include <sys/bus.h>
31 #include <sys/kernel.h>
32 #include <sys/module.h>
33 #include <sys/gpio.h>
34 
35 #include <dev/gpio/gpiobusvar.h>
36 #include <dev/ofw/ofw_bus.h>
37 #include <dev/ofw/ofw_bus_subr.h>
38 
39 #include <dev/clk/clk.h>
40 
41 #include "mmc_pwrseq_if.h"
42 
43 enum pwrseq_type {
44 	PWRSEQ_SIMPLE = 1,
45 	PWRSEQ_EMMC,
46 };
47 
48 static struct ofw_compat_data compat_data[] = {
49 	{ "mmc-pwrseq-simple",	PWRSEQ_SIMPLE },
50 	{ "mmc-pwrseq-emmc",	PWRSEQ_EMMC },
51 	{ NULL,			0 }
52 };
53 
54 struct mmc_pwrseq_softc {
55 	enum pwrseq_type	type;
56 	clk_t			ext_clock;
57 	struct gpiobus_pin	*reset_gpio;
58 
59 	uint32_t		post_power_on_delay_ms;
60 	uint32_t		power_off_delay_us;
61 };
62 
63 static int
64 mmc_pwrseq_probe(device_t dev)
65 {
66 	enum pwrseq_type type;
67 
68 	if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
69 		return (ENXIO);
70 
71 	type = (enum pwrseq_type)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
72 	switch (type) {
73 	case PWRSEQ_SIMPLE:
74 		device_set_desc(dev, "MMC Simple Power sequence");
75 		break;
76 	case PWRSEQ_EMMC:
77 		device_set_desc(dev, "MMC eMMC Power sequence");
78 		break;
79 	}
80 	return (BUS_PROBE_DEFAULT);
81 }
82 
83 static int
84 mmc_pwrseq_attach(device_t dev)
85 {
86 	struct mmc_pwrseq_softc *sc;
87 	phandle_t node;
88 	int rv;
89 
90 	sc = device_get_softc(dev);
91 	sc->type = (enum pwrseq_type)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
92 	node = ofw_bus_get_node(dev);
93 
94 	if (sc->type == PWRSEQ_SIMPLE) {
95 		if (OF_hasprop(node, "clocks")) {
96 			rv = clk_get_by_ofw_name(dev, 0, "ext_clock", &sc->ext_clock);
97 			if (rv != 0) {
98 				device_printf(dev,
99 				    "Node have a clocks property but no clocks named \"ext_clock\"\n");
100 				return (ENXIO);
101 			}
102 		}
103 		OF_getencprop(node, "post-power-on-delay-ms", &sc->post_power_on_delay_ms, sizeof(uint32_t));
104 		OF_getencprop(node, "power-off-delay-us", &sc->power_off_delay_us, sizeof(uint32_t));
105 	}
106 
107 	if (OF_hasprop(node, "reset-gpios")) {
108 		if (gpio_pin_get_by_ofw_property(dev, node, "reset-gpios",
109 		    &sc->reset_gpio) != 0) {
110 			device_printf(dev, "Cannot get the reset-gpios\n");
111 			return (ENXIO);
112 		}
113 		gpio_pin_setflags(sc->reset_gpio, GPIO_PIN_OUTPUT);
114 		gpio_pin_set_active(sc->reset_gpio, true);
115 	}
116 
117 	OF_device_register_xref(OF_xref_from_node(node), dev);
118 	return (0);
119 }
120 
121 static int
122 mmc_pwrseq_detach(device_t dev)
123 {
124 
125 	return (EBUSY);
126 }
127 
128 static int
129 mmv_pwrseq_set_power(device_t dev, bool power_on)
130 {
131 	struct mmc_pwrseq_softc *sc;
132 	int rv;
133 
134 	sc = device_get_softc(dev);
135 
136 	if (power_on) {
137 		if (sc->ext_clock) {
138 			rv = clk_enable(sc->ext_clock);
139 			if (rv != 0)
140 				return (rv);
141 		}
142 
143 		if (sc->reset_gpio) {
144 			rv = gpio_pin_set_active(sc->reset_gpio, false);
145 			if (rv != 0)
146 				return (rv);
147 		}
148 
149 		if (sc->post_power_on_delay_ms)
150 			DELAY(sc->post_power_on_delay_ms * 1000);
151 	} else {
152 		if (sc->reset_gpio) {
153 			rv = gpio_pin_set_active(sc->reset_gpio, true);
154 			if (rv != 0)
155 				return (rv);
156 		}
157 
158 		if (sc->ext_clock) {
159 			rv = clk_stop(sc->ext_clock);
160 			if (rv != 0)
161 				return (rv);
162 		}
163 		if (sc->power_off_delay_us)
164 			DELAY(sc->power_off_delay_us);
165 	}
166 
167 	return (0);
168 }
169 
170 static device_method_t mmc_pwrseq_methods[] = {
171 	/* Device interface */
172 	DEVMETHOD(device_probe,		mmc_pwrseq_probe),
173 	DEVMETHOD(device_attach,	mmc_pwrseq_attach),
174 	DEVMETHOD(device_detach,	mmc_pwrseq_detach),
175 
176 	DEVMETHOD(mmc_pwrseq_set_power,	mmv_pwrseq_set_power),
177 	DEVMETHOD_END
178 };
179 
180 static driver_t mmc_pwrseq_driver = {
181 	"mmc_pwrseq",
182 	mmc_pwrseq_methods,
183 	sizeof(struct mmc_pwrseq_softc),
184 };
185 
186 EARLY_DRIVER_MODULE(mmc_pwrseq, simplebus, mmc_pwrseq_driver, 0, 0,
187 	BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_FIRST);
188 MODULE_VERSION(mmc_pwrseq, 1);
189 SIMPLEBUS_PNP_INFO(compat_data);
190