1 /*-
2  * PMC (Power Management Controller of NEC PC-98Note) Driver
3  *
4  * Copyright (c) 2001 Chiharu Shibata.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
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  * 3. The name of the author may not be used to endorse or promote products
16  *    derived from this software without specific prior written permission
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  * $FreeBSD: stable/9/sys/pc98/cbus/pmc.c 130026 2004-06-03 06:10:02Z phk $
31  */
32 
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/eventhandler.h>
36 #include <sys/kernel.h>
37 #include <sys/module.h>
38 #include <sys/bus.h>
39 #include <sys/types.h>
40 #include <sys/conf.h>
41 #include <sys/reboot.h>
42 
43 #include <machine/bus.h>
44 #include <machine/resource.h>
45 #include <sys/rman.h>
46 
47 #include <isa/isavar.h>
48 
49 struct pmc_isa_softc {
50 	struct	resource	*port_res;
51 	eventhandler_tag	evt;
52 	int			flags;
53 };
54 
55 static int	pmc_isa_alloc_resources(device_t);
56 static void	pmc_isa_release_resources(device_t);
57 static int	pmc_isa_probe(device_t);
58 static int	pmc_isa_attach(device_t);
59 static int	pmc_isa_detach(device_t);
60 
61 #define	PMC_ISA_PORT		0x8f0
62 #define	PMC_ISA_PORTSIZE	4
63 
64 #define	sc_inw(sc, port) \
65 	bus_space_read_2(rman_get_bustag((sc)->port_res), \
66 		rman_get_bushandle((sc)->port_res), (port))
67 
68 #define	sc_outw(sc, port, value) \
69 	bus_space_write_2(rman_get_bustag((sc)->port_res), \
70 		rman_get_bushandle((sc)->port_res), (port), (value))
71 
72 static void
pmc_poweroff(void * arg,int howto)73 pmc_poweroff(void *arg, int howto)
74 {
75 	struct pmc_isa_softc *sc = (struct pmc_isa_softc *)arg;
76 
77 	if (!sc->flags) {
78 		outb(0x5e8e, inb(0x5e8e) & ~0x11);	/* FDD LED off */
79 	}
80 
81 	if (!(howto & RB_POWEROFF)) {
82 		return;
83 	}
84 
85 	sc_outw(sc, 0, 0x0044);
86 	sc_outw(sc, 2, 1 << 10);
87 #if 1
88 	/* for 9801NS/T */
89 	sc_outw(sc, 0, 0xf00a);
90 	sc_outw(sc, 2, 1 << 9);
91 #endif
92 }
93 
94 static int
pmc_isa_alloc_resources(device_t dev)95 pmc_isa_alloc_resources(device_t dev)
96 {
97 	struct pmc_isa_softc *sc = device_get_softc(dev);
98 	int	rid;
99 
100 	bzero(sc, sizeof(*sc));
101 
102 	rid = 0;
103 	sc->port_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
104 					  0ul, ~0ul, PMC_ISA_PORTSIZE,
105 					  RF_ACTIVE);
106 	if (sc->port_res == NULL) {
107 		return (ENOMEM);
108 	}
109 
110 	return 0;
111 }
112 
113 static void
pmc_isa_release_resources(device_t dev)114 pmc_isa_release_resources(device_t dev)
115 {
116 	struct pmc_isa_softc *sc = device_get_softc(dev);
117 
118 	if (sc->port_res != NULL) {
119 		bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->port_res);
120 	}
121 	sc->port_res = NULL;
122 }
123 
124 static int
pmc_isa_probe(device_t dev)125 pmc_isa_probe(device_t dev)
126 {
127 	struct pmc_isa_softc *sc = device_get_softc(dev);
128 	u_int	port;
129 	u_int16_t	save, tmp;
130 
131 #if 0
132 	if (isa_get_vendorid(dev)) {
133 		return ENXIO;
134 	}
135 	if (device_get_unit(dev) > 0) {
136 		printf("pmc: Only one PMC driver supported.\n");
137 		return ENXIO;
138 	}
139 #endif
140 	port = isa_get_port(dev);
141 	if (port == -1) {
142 		port = PMC_ISA_PORT;
143 	}
144 	if (bootverbose) {
145 		device_printf(dev, "port = 0x%x\n", port);
146 	}
147 
148 	if (bus_set_resource(dev, SYS_RES_IOPORT, 0, port, PMC_ISA_PORTSIZE)) {
149 		if (bootverbose) {
150 			device_printf(dev, "bus_set_resource failed\n");
151 		}
152 		return ENXIO;
153 	}
154 	if (pmc_isa_alloc_resources(dev)) {
155 		if (bootverbose) {
156 			device_printf(dev, "pmc_isa_alloc_resources failed\n");
157 		}
158 		return ENXIO;
159 	}
160 
161 	/* Check the existence of PMC */
162 	sc_outw(sc, 0, 0x0052);
163 	save = sc_inw(sc, 2);
164 	tmp = save & ~0x3f;
165 	sc_outw(sc, 2, tmp);
166 	if (sc_inw(sc, 2) != tmp) {
167 		if (bootverbose) {
168 			device_printf(dev, "failed to clear index(0x0052)\n");
169 		}
170 
171 		pmc_isa_release_resources(dev);
172 		return ENXIO;
173 	}
174 
175 	tmp |= 0x3e;
176 	sc_outw(sc, 2, tmp);
177 	if (sc_inw(sc, 2) != tmp) {
178 		if (bootverbose) {
179 			device_printf(dev, "failed to set index(0x0052)\n");
180 		}
181 
182 		pmc_isa_release_resources(dev);
183 		return ENXIO;
184 	}
185 	sc_outw(sc, 2, save);
186 
187 	pmc_isa_release_resources(dev);
188 
189 	device_set_desc(dev, "Power Management Controller");
190 	return 0;
191 }
192 
193 static int
pmc_isa_attach(device_t dev)194 pmc_isa_attach(device_t dev)
195 {
196 	struct pmc_isa_softc *sc = device_get_softc(dev);
197 	int error;
198 
199 	error = pmc_isa_alloc_resources(dev);
200 	if (error) {
201 		device_printf(dev, "resource allocation failed\n");
202 		return error;
203 	}
204 
205 	/* Power the system off using PMC */
206 	sc->evt = EVENTHANDLER_REGISTER(shutdown_final, pmc_poweroff, sc,
207 					SHUTDOWN_PRI_LAST);
208 	sc->flags = device_get_flags(dev);
209 	return 0;
210 }
211 
212 static int
pmc_isa_detach(device_t dev)213 pmc_isa_detach(device_t dev)
214 {
215 	struct pmc_isa_softc *sc = device_get_softc(dev);
216 
217 	if (bootverbose) {
218 		device_printf(dev, "pmc_isa_detach called\n");
219 	}
220 
221 	if (sc->evt != NULL) {
222 		EVENTHANDLER_DEREGISTER(shutdown_final, sc->evt);
223 	}
224 	sc->evt = NULL;
225 
226 	pmc_isa_release_resources(dev);
227 	return 0;
228 }
229 
230 static device_method_t pmc_isa_methods[] = {
231 	/* Device interface */
232 	DEVMETHOD(device_probe,		pmc_isa_probe),
233 	DEVMETHOD(device_attach,	pmc_isa_attach),
234 	DEVMETHOD(device_detach,	pmc_isa_detach),
235 	{0, 0}
236 };
237 
238 static driver_t pmc_isa_driver = {
239 	"pmc",
240 	pmc_isa_methods, sizeof(struct pmc_isa_softc),
241 };
242 
243 devclass_t pmc_devclass;
244 
245 DRIVER_MODULE(pmc, isa, pmc_isa_driver, pmc_devclass, 0, 0);
246