xref: /openbsd/sys/arch/loongson/dev/apm.c (revision fc9b224b)
1 /*	$OpenBSD: apm.c,v 1.34 2020/02/16 23:37:23 jca Exp $	*/
2 
3 /*-
4  * Copyright (c) 2001 Alexander Guy.  All rights reserved.
5  * Copyright (c) 1998-2001 Michael Shalayeff. All rights reserved.
6  * Copyright (c) 1995 John T. Kohl.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the names of the authors nor the names of contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  */
33 
34 #include "apm.h"
35 #include "wsdisplay.h"
36 
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/kernel.h>
40 #include <sys/proc.h>
41 #include <sys/device.h>
42 #include <sys/fcntl.h>
43 #include <sys/ioctl.h>
44 #include <sys/buf.h>
45 #include <sys/event.h>
46 #include <sys/reboot.h>
47 #include <sys/hibernate.h>
48 #include <dev/rndvar.h>
49 
50 #include <machine/autoconf.h>
51 #include <machine/conf.h>
52 #include <machine/cpu.h>
53 #include <machine/apmvar.h>
54 
55 #include <dev/pci/pcivar.h>    /* pci_dopm */
56 
57 #include <dev/wscons/wsdisplayvar.h>
58 
59 #include <loongson/dev/kb3310var.h>
60 
61 #if defined(APMDEBUG)
62 #define DPRINTF(x)	printf x
63 #else
64 #define	DPRINTF(x)	/**/
65 #endif
66 
67 struct apm_softc {
68 	struct device sc_dev;
69 	struct klist sc_note;
70 	int    sc_flags;
71 };
72 
73 int apmmatch(struct device *, void *, void *);
74 void apmattach(struct device *, struct device *, void *);
75 
76 struct cfattach apm_ca = {
77 	sizeof(struct apm_softc), apmmatch, apmattach
78 };
79 
80 struct cfdriver apm_cd = {
81 	NULL, "apm", DV_DULL
82 };
83 
84 #define	APMUNIT(dev)	(minor(dev)&0xf0)
85 #define	APMDEV(dev)	(minor(dev)&0x0f)
86 #define APMDEV_NORMAL	0
87 #define APMDEV_CTL	8
88 
89 void filt_apmrdetach(struct knote *kn);
90 int filt_apmread(struct knote *kn, long hint);
91 int apmkqfilter(dev_t dev, struct knote *kn);
92 int apm_getdefaultinfo(struct apm_power_info *);
93 
94 int apm_suspend(int state);
95 
96 const struct filterops apmread_filtops = {
97 	.f_isfd		= 1,
98 	.f_attach	= NULL,
99 	.f_detach	= filt_apmrdetach,
100 	.f_event	= filt_apmread,
101 };
102 
103 int (*get_apminfo)(struct apm_power_info *) = apm_getdefaultinfo;
104 
105 /*
106  * Flags to control kernel display
107  *	SCFLAG_NOPRINT:		do not output APM power messages due to
108  *				a power change event.
109  *
110  *	SCFLAG_PCTPRINT:	do not output APM power messages due to
111  *				to a power change event unless the battery
112  *				percentage changes.
113  */
114 
115 #define SCFLAG_NOPRINT	0x0008000
116 #define SCFLAG_PCTPRINT	0x0004000
117 #define SCFLAG_PRINT	(SCFLAG_NOPRINT|SCFLAG_PCTPRINT)
118 
119 #define	SCFLAG_OREAD 	(1 << 0)
120 #define	SCFLAG_OWRITE	(1 << 1)
121 #define	SCFLAG_OPEN	(SCFLAG_OREAD|SCFLAG_OWRITE)
122 
123 
124 int
125 apmmatch(struct device *parent, void *match, void *aux)
126 {
127 	struct mainbus_attach_args *maa = aux;
128 
129 	/*
130 	 * It only makes sense to attach on a 2F system, since 2E do not
131 	 * feature speed throttling, and we do not support 2E-based
132 	 * notebooks yet (assuming there are any).
133 	 */
134 	if (strcmp(maa->maa_name, apm_cd.cd_name) == 0 && loongson_ver == 0x2f)
135 		return (1);
136 	return (0);
137 }
138 
139 void
140 apmattach(struct device *parent, struct device *self, void *aux)
141 {
142 	/* Enable PCI Power Management. */
143 	pci_dopm = 1;
144 
145 	printf("\n");
146 }
147 
148 int
149 apmopen(dev_t dev, int flag, int mode, struct proc *p)
150 {
151 	struct apm_softc *sc;
152 	int error = 0;
153 
154 	/* apm0 only */
155 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
156 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
157 		return ENXIO;
158 
159 	DPRINTF(("apmopen: dev %d pid %d flag %x mode %x\n",
160 	    APMDEV(dev), p->p_p->ps_pid, flag, mode));
161 
162 	switch (APMDEV(dev)) {
163 	case APMDEV_CTL:
164 		if (!(flag & FWRITE)) {
165 			error = EINVAL;
166 			break;
167 		}
168 		if (sc->sc_flags & SCFLAG_OWRITE) {
169 			error = EBUSY;
170 			break;
171 		}
172 		sc->sc_flags |= SCFLAG_OWRITE;
173 		break;
174 	case APMDEV_NORMAL:
175 		if (!(flag & FREAD) || (flag & FWRITE)) {
176 			error = EINVAL;
177 			break;
178 		}
179 		sc->sc_flags |= SCFLAG_OREAD;
180 		break;
181 	default:
182 		error = ENXIO;
183 		break;
184 	}
185 	return error;
186 }
187 
188 int
189 apmclose(dev_t dev, int flag, int mode, struct proc *p)
190 {
191 	struct apm_softc *sc;
192 
193 	/* apm0 only */
194 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
195 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
196 		return ENXIO;
197 
198 	DPRINTF(("apmclose: pid %d flag %x mode %x\n",
199 	    p->p_p->ps_pid, flag, mode));
200 
201 	switch (APMDEV(dev)) {
202 	case APMDEV_CTL:
203 		sc->sc_flags &= ~SCFLAG_OWRITE;
204 		break;
205 	case APMDEV_NORMAL:
206 		sc->sc_flags &= ~SCFLAG_OREAD;
207 		break;
208 	}
209 	return 0;
210 }
211 
212 int
213 apmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
214 {
215 	struct apm_softc *sc;
216 	struct apm_power_info *power;
217 	int error = 0;
218 
219 	/* apm0 only */
220 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
221 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
222 		return ENXIO;
223 
224 	switch (cmd) {
225 		/* some ioctl names from linux */
226 	case APM_IOC_STANDBY:
227 	case APM_IOC_STANDBY_REQ:
228 	case APM_IOC_SUSPEND:
229 	case APM_IOC_SUSPEND_REQ:
230 		if ((flag & FWRITE) == 0)
231 			error = EBADF;
232 		else if (sys_platform->suspend == NULL ||
233 		    sys_platform->resume == NULL)
234 			error = EOPNOTSUPP;
235 		else
236 			error = apm_suspend(APM_IOC_SUSPEND);
237 		break;
238 #ifdef HIBERNATE
239 	case APM_IOC_HIBERNATE:
240 		if ((flag & FWRITE) == 0)
241 			error = EBADF;
242 		else if (sys_platform->suspend == NULL ||
243 		    sys_platform->resume == NULL)
244 			error = EOPNOTSUPP;
245 		else
246 			error = apm_suspend(APM_IOC_HIBERNATE);
247 		break;
248 #endif
249 	case APM_IOC_PRN_CTL:
250 		if ((flag & FWRITE) == 0)
251 			error = EBADF;
252 		else {
253 			int flag = *(int *)data;
254 			DPRINTF(( "APM_IOC_PRN_CTL: %d\n", flag ));
255 			switch (flag) {
256 			case APM_PRINT_ON:	/* enable printing */
257 				sc->sc_flags &= ~SCFLAG_PRINT;
258 				break;
259 			case APM_PRINT_OFF: /* disable printing */
260 				sc->sc_flags &= ~SCFLAG_PRINT;
261 				sc->sc_flags |= SCFLAG_NOPRINT;
262 				break;
263 			case APM_PRINT_PCT: /* disable some printing */
264 				sc->sc_flags &= ~SCFLAG_PRINT;
265 				sc->sc_flags |= SCFLAG_PCTPRINT;
266 				break;
267 			default:
268 				error = EINVAL;
269 				break;
270 			}
271 		}
272 		break;
273 	case APM_IOC_DEV_CTL:
274 		if ((flag & FWRITE) == 0)
275 			error = EBADF;
276 		else
277 			error = EOPNOTSUPP; /* XXX */
278 		break;
279 	case APM_IOC_GETPOWER:
280 	        power = (struct apm_power_info *)data;
281 		error = (*get_apminfo)(power);
282 		break;
283 	default:
284 		error = ENOTTY;
285 	}
286 
287 	return error;
288 }
289 
290 void
291 filt_apmrdetach(struct knote *kn)
292 {
293 	struct apm_softc *sc = (struct apm_softc *)kn->kn_hook;
294 
295 	SLIST_REMOVE(&sc->sc_note, kn, knote, kn_selnext);
296 }
297 
298 int
299 filt_apmread(struct knote *kn, long hint)
300 {
301 	/* XXX weird kqueue_scan() semantics */
302 	if (hint && !kn->kn_data)
303 		kn->kn_data = (int)hint;
304 
305 	return (1);
306 }
307 
308 int
309 apmkqfilter(dev_t dev, struct knote *kn)
310 {
311 	struct apm_softc *sc;
312 
313 	/* apm0 only */
314 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
315 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
316 		return ENXIO;
317 
318 	switch (kn->kn_filter) {
319 	case EVFILT_READ:
320 		kn->kn_fop = &apmread_filtops;
321 		break;
322 	default:
323 		return (EINVAL);
324 	}
325 
326 	kn->kn_hook = (caddr_t)sc;
327 	SLIST_INSERT_HEAD(&sc->sc_note, kn, kn_selnext);
328 
329 	return (0);
330 }
331 
332 int
333 apm_getdefaultinfo(struct apm_power_info *info)
334 {
335 	info->battery_state = APM_BATT_UNKNOWN;
336 	info->ac_state = APM_AC_UNKNOWN;
337 	info->battery_life = 0;
338 	info->minutes_left = -1;
339 	return (0);
340 }
341 
342 void
343 apm_setinfohook(int (*hook)(struct apm_power_info *))
344 {
345 	get_apminfo = hook;
346 }
347 
348 int
349 apm_record_event(u_int event, const char *src, const char *msg)
350 {
351 	static int apm_evindex;
352 	struct apm_softc *sc;
353 
354 	/* apm0 only */
355 	if (apm_cd.cd_ndevs == 0 || (sc = apm_cd.cd_devs[0]) == NULL)
356 		return ENXIO;
357 
358 	if ((sc->sc_flags & SCFLAG_NOPRINT) == 0)
359 		printf("%s: %s %s\n", sc->sc_dev.dv_xname, src, msg);
360 
361 	/* skip if no user waiting */
362 	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
363 		return (1);
364 
365 	apm_evindex++;
366 	KNOTE(&sc->sc_note, APM_EVENT_COMPOSE(event, apm_evindex));
367 
368 	return (0);
369 }
370 
371 int
372 apm_suspend(int state)
373 {
374 	int rv;
375 	int s;
376 
377 #if NWSDISPLAY > 0
378 	wsdisplay_suspend();
379 #endif
380 
381 	stop_periodic_resettodr();
382 	resettodr();
383 
384 	config_suspend_all(DVACT_QUIESCE);
385 	bufq_quiesce();
386 
387 	s = splhigh();
388 	(void)disableintr();
389 	cold = 2;
390 
391 	rv = config_suspend_all(DVACT_SUSPEND);
392 
393 	suspend_randomness();
394 
395 #ifdef HIBERNATE
396 	if (state == APM_IOC_HIBERNATE) {
397 		uvm_pmr_zero_everything();
398 		if (hibernate_suspend()) {
399 			printf("apm: hibernate_suspend failed");
400 			uvm_pmr_dirty_everything();
401 			return (ECANCELED);
402 		}
403 	}
404 #endif
405 
406 	/* XXX
407 	 * Flag to disk drivers that they should "power down" the disk
408 	 * when we get to DVACT_POWERDOWN.
409 	 */
410 	boothowto |= RB_POWERDOWN;
411 	config_suspend_all(DVACT_POWERDOWN);
412 	boothowto &= ~RB_POWERDOWN;
413 
414 	if (rv == 0) {
415 		rv = sys_platform->suspend();
416 		if (rv == 0)
417 			rv = sys_platform->resume();
418 	}
419 	inittodr(time_second);	/* Move the clock forward */
420 	config_suspend_all(DVACT_RESUME);
421 
422 	cold = 0;
423 	(void)enableintr();
424 	splx(s);
425 
426 	resume_randomness(NULL, 0);	/* force RNG upper level reseed */
427 	bufq_restart();
428 
429 	config_suspend_all(DVACT_WAKEUP);
430 
431 	start_periodic_resettodr();
432 
433 #if NWSDISPLAY > 0
434 	wsdisplay_resume();
435 #endif
436 
437 	apm_record_event(APM_NORMAL_RESUME, "System", "resumed from sleep");
438 
439 	return rv;
440 }
441