/*-
* Copyright (c) 1996, 1997 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by John Kohl and Christopher G. Demetriou.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* from: sys/arch/i386/i386/apm.c,v 1.49 2000/05/08
*/
/*
* A brief note on the locking protocol: it's very simple; we
* assert an exclusive lock any time thread context enters the
* APM module. This is both the APM thread itself, as well as
* user context.
*/
#define APM_LOCK(apmsc) \
(void) mutex_enter(&(apmsc)->sc_lock)
#define APM_UNLOCK(apmsc) \
(void) mutex_exit(&(apmsc)->sc_lock)
/* configurable variables */
#ifdef APM_NO_STANDBY
int apm_do_standby = 0;
#else
int apm_do_standby = 1;
#endif
#ifdef APM_V10_ONLY
int apm_v11_enabled = 0;
#else
int apm_v11_enabled = 1;
#endif
#ifdef APM_NO_V12
int apm_v12_enabled = 0;
#else
int apm_v12_enabled = 1;
#endif
/* variables used during operation (XXX cgd) */
u_char apm_majver, apm_minver;
int apm_inited;
int apm_standbys, apm_userstandbys, apm_suspends, apm_battlow;
int apm_damn_fool_bios, apm_op_inprog;
int apm_evindex;
static int apm_spl; /* saved spl while suspended */
const char *
apm_strerror(int code)
{
switch (code) {
case APM_ERR_PM_DISABLED:
return ("power management disabled");
case APM_ERR_REALALREADY:
return ("real mode interface already connected");
case APM_ERR_NOTCONN:
return ("interface not connected");
case APM_ERR_16ALREADY:
return ("16-bit interface already connected");
case APM_ERR_16NOTSUPP:
return ("16-bit interface not supported");
case APM_ERR_32ALREADY:
return ("32-bit interface already connected");
case APM_ERR_32NOTSUPP:
return ("32-bit interface not supported");
case APM_ERR_UNRECOG_DEV:
return ("unrecognized device ID");
case APM_ERR_ERANGE:
return ("parameter out of range");
case APM_ERR_NOTENGAGED:
return ("interface not engaged");
case APM_ERR_UNABLE:
return ("unable to enter requested state");
case APM_ERR_NOEVENTS:
return ("no pending events");
case APM_ERR_NOT_PRESENT:
return ("no APM present");
default:
return ("unknown error code");
}
}
#if 0 /* XXX: def TIME_FREQ */
/*
* Some system requires its clock to be initialized after hybernation.
*/
initrtclock(TIMER_FREQ);
#endif
inittodr(time_second);
dopowerhooks(PWR_RESUME);
splx(apm_spl);
dopowerhooks(PWR_SOFTRESUME);
apm_record_event(sc, event_type);
}
/*
* return 0 if the user will notice and handle the event,
* return 1 if the kernel driver should do so.
*/
static int
apm_record_event(struct apm_softc *sc, u_int event_type)
{
struct apm_event_info *evp;
if ((sc->sc_flags & SCFLAG_OPEN) == 0)
return 1; /* no user waiting */
if (sc->sc_event_count == APM_NEVENTS)
return 1; /* overflow */
evp = &sc->sc_event_list[sc->sc_event_ptr];
sc->sc_event_count++;
sc->sc_event_ptr++;
sc->sc_event_ptr %= APM_NEVENTS;
evp->type = event_type;
evp->index = ++apm_evindex;
selnotify(&sc->sc_rsel, 0, 0);
return (sc->sc_flags & SCFLAG_OWRITE) ? 0 : 1; /* user may handle */
}
switch (event_code) {
case APM_USER_STANDBY_REQ:
DPRINTF(APMDEBUG_EVENTS, ("apmev: user standby request\n"));
if (apm_do_standby) {
if (apm_op_inprog == 0 && apm_record_event(sc, event_code))
apm_userstandbys++;
apm_op_inprog++;
(void)(*sc->sc_ops->aa_set_powstate)(sc->sc_cookie,
APM_DEV_ALLDEVS, APM_LASTREQ_INPROG);
} else {
(void)(*sc->sc_ops->aa_set_powstate)(sc->sc_cookie,
APM_DEV_ALLDEVS, APM_LASTREQ_REJECTED);
/* in case BIOS hates being spurned */
(*sc->sc_ops->aa_enable)(sc->sc_cookie, 1);
}
break;
case APM_STANDBY_REQ:
DPRINTF(APMDEBUG_EVENTS, ("apmev: system standby request\n"));
if (apm_standbys || apm_suspends) {
DPRINTF(APMDEBUG_EVENTS | APMDEBUG_ANOM,
("damn fool BIOS did not wait for answer\n"));
/* just give up the fight */
apm_damn_fool_bios = 1;
}
if (apm_do_standby) {
if (apm_op_inprog == 0 &&
apm_record_event(sc, event_code))
apm_standbys++;
apm_op_inprog++;
(void)(*sc->sc_ops->aa_set_powstate)(sc->sc_cookie,
APM_DEV_ALLDEVS, APM_LASTREQ_INPROG);
} else {
(void)(*sc->sc_ops->aa_set_powstate)(sc->sc_cookie,
APM_DEV_ALLDEVS, APM_LASTREQ_REJECTED);
/* in case BIOS hates being spurned */
(*sc->sc_ops->aa_enable)(sc->sc_cookie, 1);
}
break;
case APM_USER_SUSPEND_REQ:
DPRINTF(APMDEBUG_EVENTS, ("apmev: user suspend request\n"));
if (apm_op_inprog == 0 && apm_record_event(sc, event_code))
apm_suspends++;
apm_op_inprog++;
(void)(*sc->sc_ops->aa_set_powstate)(sc->sc_cookie,
APM_DEV_ALLDEVS, APM_LASTREQ_INPROG);
break;
case APM_SUSPEND_REQ:
DPRINTF(APMDEBUG_EVENTS, ("apmev: system suspend request\n"));
if (apm_standbys || apm_suspends) {
DPRINTF(APMDEBUG_EVENTS | APMDEBUG_ANOM,
("damn fool BIOS did not wait for answer\n"));
/* just give up the fight */
apm_damn_fool_bios = 1;
}
if (apm_op_inprog == 0 && apm_record_event(sc, event_code))
apm_suspends++;
apm_op_inprog++;
(void)(*sc->sc_ops->aa_set_powstate)(sc->sc_cookie,
APM_DEV_ALLDEVS, APM_LASTREQ_INPROG);
break;
case APM_POWER_CHANGE:
DPRINTF(APMDEBUG_EVENTS, ("apmev: power status change\n"));
error = (*sc->sc_ops->aa_get_powstat)(sc->sc_cookie, 0, &pi);
#ifdef APM_POWER_PRINT
/* only print if nobody is catching events. */
if (error == 0 &&
(sc->sc_flags & (SCFLAG_OREAD|SCFLAG_OWRITE)) == 0)
apm_power_print(sc, &pi);
#else
__USE(error);
#endif
apm_record_event(sc, event_code);
break;
case APM_NORMAL_RESUME:
DPRINTF(APMDEBUG_EVENTS, ("apmev: resume system\n"));
apm_resume(sc, event_code, event_info);
break;
/*
* tell the BIOS we're working on it, if asked to do a
* suspend/standby
*/
if (apm_op_inprog)
(*sc->sc_ops->aa_set_powstate)(sc->sc_cookie, APM_DEV_ALLDEVS,
APM_LASTREQ_INPROG);
switch ((APM_MAJOR_VERS(sc->sc_vers) << 8) + APM_MINOR_VERS(sc->sc_vers)) {
case 0x0100:
apm_v11_enabled = 0;
apm_v12_enabled = 0;
break;
case 0x0101:
apm_v12_enabled = 0;
/* fall through */
case 0x0102:
default:
break;
}
apm_set_ver(sc); /* prints version info */
aprint_normal("\n");
if (apm_minver >= 2)
(*sc->sc_ops->aa_get_capabilities)(sc->sc_cookie, &numbatts,
&capflags);
/*
* enable power management
*/
(*sc->sc_ops->aa_enable)(sc->sc_cookie, 1);
/* Initial state is `resumed'. */
sc->sc_power_state = PWR_RESUME;
selinit(&sc->sc_rsel);
selinit(&sc->sc_xsel);
/* Do an initial check. */
apm_periodic_check(sc);
/*
* Create a kernel thread to periodically check for APM events,
* and notify other subsystems when they occur.
*/
if (kthread_create(PRI_NONE, 0, NULL, apm_thread, sc,
&sc->sc_thread, "%s", device_xname(sc->sc_dev)) != 0) {
/*
* We were unable to create the APM thread; bail out.
*/
if (sc->sc_ops->aa_disconnect)
(*sc->sc_ops->aa_disconnect)(sc->sc_cookie);
aprint_error_dev(sc->sc_dev, "unable to create thread, "
"kernel APM support disabled\n");
}
}