xref: /openbsd/sys/arch/loongson/dev/apm.c (revision 6658f7ae)
1 /*	$OpenBSD: apm.c,v 1.22 2014/07/19 12:54:09 pirofti 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 
49 #include <machine/autoconf.h>
50 #include <machine/conf.h>
51 #include <machine/cpu.h>
52 #include <machine/apmvar.h>
53 
54 #include <dev/wscons/wsdisplayvar.h>
55 
56 #include <loongson/dev/kb3310var.h>
57 
58 #if defined(APMDEBUG)
59 #define DPRINTF(x)	printf x
60 #else
61 #define	DPRINTF(x)	/**/
62 #endif
63 
64 struct apm_softc {
65 	struct device sc_dev;
66 	struct klist sc_note;
67 	int    sc_flags;
68 };
69 
70 int apmmatch(struct device *, void *, void *);
71 void apmattach(struct device *, struct device *, void *);
72 
73 struct cfattach apm_ca = {
74 	sizeof(struct apm_softc), apmmatch, apmattach
75 };
76 
77 struct cfdriver apm_cd = {
78 	NULL, "apm", DV_DULL
79 };
80 
81 #define	APMUNIT(dev)	(minor(dev)&0xf0)
82 #define	APMDEV(dev)	(minor(dev)&0x0f)
83 #define APMDEV_NORMAL	0
84 #define APMDEV_CTL	8
85 
86 void filt_apmrdetach(struct knote *kn);
87 int filt_apmread(struct knote *kn, long hint);
88 int apmkqfilter(dev_t dev, struct knote *kn);
89 int apm_getdefaultinfo(struct apm_power_info *);
90 
91 int apm_suspend(int state);
92 
93 struct filterops apmread_filtops =
94 	{ 1, NULL, filt_apmrdetach, filt_apmread};
95 
96 int (*get_apminfo)(struct apm_power_info *) = apm_getdefaultinfo;
97 
98 /*
99  * Flags to control kernel display
100  *	SCFLAG_NOPRINT:		do not output APM power messages due to
101  *				a power change event.
102  *
103  *	SCFLAG_PCTPRINT:	do not output APM power messages due to
104  *				to a power change event unless the battery
105  *				percentage changes.
106  */
107 
108 #define SCFLAG_NOPRINT	0x0008000
109 #define SCFLAG_PCTPRINT	0x0004000
110 #define SCFLAG_PRINT	(SCFLAG_NOPRINT|SCFLAG_PCTPRINT)
111 
112 #define	SCFLAG_OREAD 	(1 << 0)
113 #define	SCFLAG_OWRITE	(1 << 1)
114 #define	SCFLAG_OPEN	(SCFLAG_OREAD|SCFLAG_OWRITE)
115 
116 
117 int
118 apmmatch(struct device *parent, void *match, void *aux)
119 {
120 	struct mainbus_attach_args *maa = aux;
121 
122 	/*
123 	 * It only makes sense to attach on a 2F system, since 2E do not
124 	 * feature speed throttling, and we do not support 2E-based
125 	 * notebooks yet (assuming there are any).
126 	 */
127 	if (strcmp(maa->maa_name, apm_cd.cd_name) == 0 && loongson_ver == 0x2f)
128 		return (1);
129 	return (0);
130 }
131 
132 void
133 apmattach(struct device *parent, struct device *self, void *aux)
134 {
135 	printf("\n");
136 }
137 
138 int
139 apmopen(dev_t dev, int flag, int mode, struct proc *p)
140 {
141 	struct apm_softc *sc;
142 	int error = 0;
143 
144 	/* apm0 only */
145 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
146 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
147 		return ENXIO;
148 
149 	DPRINTF(("apmopen: dev %d pid %d flag %x mode %x\n",
150 	    APMDEV(dev), p->p_pid, flag, mode));
151 
152 	switch (APMDEV(dev)) {
153 	case APMDEV_CTL:
154 		if (!(flag & FWRITE)) {
155 			error = EINVAL;
156 			break;
157 		}
158 		if (sc->sc_flags & SCFLAG_OWRITE) {
159 			error = EBUSY;
160 			break;
161 		}
162 		sc->sc_flags |= SCFLAG_OWRITE;
163 		break;
164 	case APMDEV_NORMAL:
165 		if (!(flag & FREAD) || (flag & FWRITE)) {
166 			error = EINVAL;
167 			break;
168 		}
169 		sc->sc_flags |= SCFLAG_OREAD;
170 		break;
171 	default:
172 		error = ENXIO;
173 		break;
174 	}
175 	return error;
176 }
177 
178 int
179 apmclose(dev_t dev, int flag, int mode, struct proc *p)
180 {
181 	struct apm_softc *sc;
182 
183 	/* apm0 only */
184 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
185 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
186 		return ENXIO;
187 
188 	DPRINTF(("apmclose: pid %d flag %x mode %x\n", p->p_pid, flag, mode));
189 
190 	switch (APMDEV(dev)) {
191 	case APMDEV_CTL:
192 		sc->sc_flags &= ~SCFLAG_OWRITE;
193 		break;
194 	case APMDEV_NORMAL:
195 		sc->sc_flags &= ~SCFLAG_OREAD;
196 		break;
197 	}
198 	return 0;
199 }
200 
201 int
202 apmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
203 {
204 	struct apm_softc *sc;
205 	struct apm_power_info *power;
206 	int error = 0;
207 
208 	/* apm0 only */
209 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
210 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
211 		return ENXIO;
212 
213 	switch (cmd) {
214 		/* some ioctl names from linux */
215 	case APM_IOC_STANDBY:
216 	case APM_IOC_STANDBY_REQ:
217 	case APM_IOC_SUSPEND:
218 	case APM_IOC_SUSPEND_REQ:
219 		if ((flag & FWRITE) == 0)
220 			error = EBADF;
221 		else if (sys_platform->suspend == NULL ||
222 		    sys_platform->resume == NULL)
223 			error = EOPNOTSUPP;
224 		else
225 			error = apm_suspend(APM_IOC_SUSPEND);
226 		break;
227 #ifdef HIBERNATE
228 	case APM_IOC_HIBERNATE:
229 		if ((flag & FWRITE) == 0)
230 			error = EBADF;
231 		else if (sys_platform->suspend == NULL ||
232 		    sys_platform->resume == NULL)
233 			error = EOPNOTSUPP;
234 		else
235 			error = apm_suspend(APM_IOC_HIBERNATE);
236 		break;
237 #endif
238 	case APM_IOC_PRN_CTL:
239 		if ((flag & FWRITE) == 0)
240 			error = EBADF;
241 		else {
242 			int flag = *(int *)data;
243 			DPRINTF(( "APM_IOC_PRN_CTL: %d\n", flag ));
244 			switch (flag) {
245 			case APM_PRINT_ON:	/* enable printing */
246 				sc->sc_flags &= ~SCFLAG_PRINT;
247 				break;
248 			case APM_PRINT_OFF: /* disable printing */
249 				sc->sc_flags &= ~SCFLAG_PRINT;
250 				sc->sc_flags |= SCFLAG_NOPRINT;
251 				break;
252 			case APM_PRINT_PCT: /* disable some printing */
253 				sc->sc_flags &= ~SCFLAG_PRINT;
254 				sc->sc_flags |= SCFLAG_PCTPRINT;
255 				break;
256 			default:
257 				error = EINVAL;
258 				break;
259 			}
260 		}
261 		break;
262 	case APM_IOC_DEV_CTL:
263 		if ((flag & FWRITE) == 0)
264 			error = EBADF;
265 		else
266 			error = EOPNOTSUPP; /* XXX */
267 		break;
268 	case APM_IOC_GETPOWER:
269 	        power = (struct apm_power_info *)data;
270 		error = (*get_apminfo)(power);
271 		break;
272 	default:
273 		error = ENOTTY;
274 	}
275 
276 	return error;
277 }
278 
279 void
280 filt_apmrdetach(struct knote *kn)
281 {
282 	struct apm_softc *sc = (struct apm_softc *)kn->kn_hook;
283 
284 	SLIST_REMOVE(&sc->sc_note, kn, knote, kn_selnext);
285 }
286 
287 int
288 filt_apmread(struct knote *kn, long hint)
289 {
290 	/* XXX weird kqueue_scan() semantics */
291 	if (hint && !kn->kn_data)
292 		kn->kn_data = (int)hint;
293 
294 	return (1);
295 }
296 
297 int
298 apmkqfilter(dev_t dev, struct knote *kn)
299 {
300 	struct apm_softc *sc;
301 
302 	/* apm0 only */
303 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
304 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
305 		return ENXIO;
306 
307 	switch (kn->kn_filter) {
308 	case EVFILT_READ:
309 		kn->kn_fop = &apmread_filtops;
310 		break;
311 	default:
312 		return (EINVAL);
313 	}
314 
315 	kn->kn_hook = (caddr_t)sc;
316 	SLIST_INSERT_HEAD(&sc->sc_note, kn, kn_selnext);
317 
318 	return (0);
319 }
320 
321 int
322 apm_getdefaultinfo(struct apm_power_info *info)
323 {
324 	info->battery_state = APM_BATT_UNKNOWN;
325 	info->ac_state = APM_AC_UNKNOWN;
326 	info->battery_life = 0;
327 	info->minutes_left = -1;
328 	return (0);
329 }
330 
331 void
332 apm_setinfohook(int (*hook)(struct apm_power_info *))
333 {
334 	get_apminfo = hook;
335 }
336 
337 int
338 apm_record_event(u_int event, const char *src, const char *msg)
339 {
340 	static int apm_evindex;
341 	struct apm_softc *sc;
342 
343 	/* apm0 only */
344 	if (apm_cd.cd_ndevs == 0 || (sc = apm_cd.cd_devs[0]) == NULL)
345 		return ENXIO;
346 
347 	if ((sc->sc_flags & SCFLAG_NOPRINT) == 0)
348 		printf("%s: %s %s\n", sc->sc_dev.dv_xname, src, msg);
349 
350 	/* skip if no user waiting */
351 	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
352 		return (1);
353 
354 	apm_evindex++;
355 	KNOTE(&sc->sc_note, APM_EVENT_COMPOSE(event, apm_evindex));
356 
357 	return (0);
358 }
359 
360 int
361 apm_suspend(int state)
362 {
363 	struct device *mainbus = device_mainbus();
364 	int rv;
365 	int s;
366 
367 #if NSWDISPLAY > 0
368 	wsdisplay_suspend();
369 #endif
370 
371 	resettodr();
372 
373 	config_suspend(mainbus, DVACT_QUIESCE);
374 	bufq_quiesce();
375 
376 	s = splhigh();
377 	(void)disableintr();
378 	cold = 1;
379 
380 	rv = config_suspend(mainbus, DVACT_SUSPEND);
381 
382 #ifdef HIBERNATE
383 	if (state == APM_IOC_HIBERNATE) {
384 		uvm_pmr_zero_everything();
385 		if (hibernate_suspend()) {
386 			printf("apm: hibernate_suspend failed");
387 			hibernate_free();
388 			uvm_pmr_dirty_everything();
389 			return (ECANCELED);
390 		}
391 	}
392 #endif
393 
394 	/* XXX
395 	 * Flag to disk drivers that they should "power down" the disk
396 	 * when we get to DVACT_POWERDOWN.
397 	 */
398 	boothowto |= RB_POWERDOWN;
399 	(void) config_suspend(mainbus, DVACT_POWERDOWN);
400 	boothowto &= ~RB_POWERDOWN;
401 
402 	if (rv == 0) {
403 		rv = sys_platform->suspend();
404 		if (rv == 0)
405 			rv = sys_platform->resume();
406 	}
407 	inittodr(time_second);	/* Move the clock forward */
408 	config_suspend(mainbus, DVACT_RESUME);
409 
410 	cold = 0;
411 	(void)enableintr();
412 	splx(s);
413 
414 	bufq_restart();
415 
416 	config_suspend(mainbus, DVACT_WAKEUP);
417 
418 #if NWSDISPLAY > 0
419 	wsdisplay_resume();
420 #endif
421 
422 	return rv;
423 }
424