Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/acpica/acpi_rtc.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause | |||||
* | |||||
* Copyright (c) 2021 Takanori Watanabe | |||||
* | |||||
* 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 AUTHOR 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 AUTHOR 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. | |||||
* | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include "opt_acpi.h" | |||||
#include <sys/param.h> | |||||
#include <sys/systm.h> | |||||
#include <sys/kernel.h> | |||||
#include <sys/bus.h> | |||||
#include <sys/clock.h> | |||||
#include <contrib/dev/acpica/include/acpi.h> | |||||
#include "acpi_if.h" | |||||
#include "clock_if.h" | |||||
#include <sys/module.h> | |||||
#include <dev/acpica/acpivar.h> | |||||
#include <sys/sysctl.h> | |||||
#define ACPI_RTC_AC_WAKE 1 | |||||
#define ACPI_RTC_DC_WAKE 2 | |||||
#define ACPI_RTC_RTC 4 | |||||
#define ACPI_RTC_RTC_MSEC 8 | |||||
#define ACPI_RTC_GWS 0x10 | |||||
#define ACPI_RTC_S4AC 0x20 | |||||
#define ACPI_RTC_S5AC 0x40 | |||||
#define ACPI_RTC_S4DC 0x80 | |||||
#define ACPI_RTC_S5DC 0x100 | |||||
#define ACPI_RTC_TZ_UNSPEC 0x7ff | |||||
struct acpi_rtc_ct{ | |||||
uint16_t year; | |||||
uint8_t month; | |||||
uint8_t day; | |||||
uint8_t hour; | |||||
uint8_t minute; | |||||
uint8_t second; | |||||
uint8_t valid; | |||||
uint16_t milisecond; | |||||
int16_t TimeZone; | |||||
uint8_t Daylight; | |||||
uint8_t pad[3]; | |||||
}__attribute((__packed__)); | |||||
static char *acpi_rtc_id[] = {"ACPI000E", NULL}; | |||||
struct acpi_rtc_softc { | |||||
int device_cap; | |||||
int tzmin; | |||||
}; | |||||
static ACPI_STATUS acpi_rtc_grt(device_t dev, struct acpi_rtc_ct *act) | |||||
{ | |||||
ACPI_BUFFER buf; | |||||
ACPI_STATUS status ; | |||||
ACPI_OBJECT *ao; | |||||
buf.Length = ACPI_ALLOCATE_BUFFER; | |||||
buf.Pointer = NULL; | |||||
status = AcpiEvaluateObject(acpi_get_handle(dev), "_GRT", NULL, &buf); | |||||
if(ACPI_FAILURE(status)){ | |||||
goto error; | |||||
} | |||||
ao = buf.Pointer; | |||||
if(ao->Type != ACPI_TYPE_BUFFER){ | |||||
status = AE_ERROR; | |||||
}else{ | |||||
memcpy(act, ao->Buffer.Pointer, sizeof(*act)); | |||||
} | |||||
AcpiOsFree(buf.Pointer); | |||||
error: | |||||
return status; | |||||
} | |||||
static ACPI_STATUS acpi_rtc_arg1_call(device_t dev, char *name, int arg, | |||||
int *rv) | |||||
{ | |||||
ACPI_OBJECT ao, reto; | |||||
ACPI_STATUS status; | |||||
ACPI_OBJECT_LIST aol; | |||||
ACPI_BUFFER buf; | |||||
ao.Type = ACPI_TYPE_INTEGER; | |||||
ao.Integer.Value = arg; | |||||
aol.Count = 1; | |||||
aol.Pointer = &ao; | |||||
buf.Length = sizeof(reto); | |||||
buf.Pointer = &reto; | |||||
status = AcpiEvaluateObjectTyped(acpi_get_handle(dev), name, | |||||
&aol, &buf, ACPI_TYPE_INTEGER); | |||||
*rv = reto.Integer.Value; | |||||
return status; | |||||
} | |||||
static ACPI_STATUS acpi_rtc_arg2_call(device_t dev, char *name, int arg1, | |||||
int arg2, int *rv) | |||||
{ | |||||
ACPI_OBJECT ao[2], reto; | |||||
ACPI_STATUS status; | |||||
ACPI_OBJECT_LIST aol; | |||||
ACPI_BUFFER buf; | |||||
ao[0].Type = ACPI_TYPE_INTEGER; | |||||
ao[0].Integer.Value = arg1; | |||||
ao[1].Type = ACPI_TYPE_INTEGER; | |||||
ao[1].Integer.Value = arg2; | |||||
aol.Count = 2; | |||||
aol.Pointer = ao; | |||||
buf.Length = sizeof(reto); | |||||
buf.Pointer = &reto; | |||||
status = AcpiEvaluateObjectTyped(acpi_get_handle(dev), name, | |||||
&aol, &buf, ACPI_TYPE_INTEGER); | |||||
*rv = reto.Integer.Value; | |||||
return status; | |||||
} | |||||
static int acpi_rtc_gettime(device_t dev, struct timespec *ts) | |||||
{ | |||||
struct clocktime ct; | |||||
struct acpi_rtc_ct grt; | |||||
int rv; | |||||
if (ACPI_FAILURE(acpi_rtc_grt(dev, &grt))) | |||||
return EINVAL; | |||||
if (grt.valid == 0) { | |||||
return EINVAL; | |||||
} | |||||
ct.year = grt.year; | |||||
ct.mon = grt.month; | |||||
ct.day = grt.day; | |||||
ct.hour = grt.hour; | |||||
ct.min = grt.minute; | |||||
ct.sec = grt.second; | |||||
ct.dow = 0; /* ignored.*/ | |||||
ct.nsec = grt.milisecond * 1000 * 1000; | |||||
rv = clock_ct_to_ts(&ct, ts); | |||||
if (rv != 0) | |||||
return rv; | |||||
/* Return UTC time when TZ is set.*/ | |||||
if (grt.TimeZone != ACPI_RTC_TZ_UNSPEC) | |||||
ts->tv_sec += grt.TimeZone * 60; | |||||
return 0; | |||||
} | |||||
static int acpi_rtc_settime(device_t dev, struct timespec *ts) | |||||
{ | |||||
struct acpi_rtc_ct srt; | |||||
struct clocktime ct; | |||||
ACPI_OBJECT ao, reto; | |||||
ACPI_STATUS status; | |||||
ACPI_OBJECT_LIST aol; | |||||
ACPI_BUFFER buf; | |||||
struct acpi_rtc_softc *sc = device_get_softc(dev); | |||||
memset(&srt, 0, sizeof(srt)); | |||||
/* Set Local time when TZ is set.*/ | |||||
if ( sc->tzmin != ACPI_RTC_TZ_UNSPEC ) | |||||
ts->tv_sec -= sc->tzmin * 60; | |||||
clock_ts_to_ct(ts, &ct); | |||||
srt.year = ct.year; | |||||
srt.month = ct.mon; | |||||
srt.day = ct.day; | |||||
srt.hour = ct.hour; | |||||
srt.minute = ct.min; | |||||
srt.second = ct.sec; | |||||
srt.milisecond = ct.nsec / 1000 / 1000 ; | |||||
srt.TimeZone = 0x7ff; | |||||
srt.Daylight = 0; | |||||
ao.Type = ACPI_TYPE_BUFFER; | |||||
ao.Buffer.Length = sizeof(srt); | |||||
ao.Buffer.Pointer = (void*)&srt; | |||||
aol.Count = 1; | |||||
aol.Pointer = &ao; | |||||
buf.Length = sizeof(reto); | |||||
buf.Pointer = &reto; | |||||
status = AcpiEvaluateObjectTyped(acpi_get_handle(dev), "_SRT", | |||||
&aol, &buf, ACPI_TYPE_INTEGER); | |||||
if (ACPI_FAILURE(status)){ | |||||
return (EINVAL); | |||||
} | |||||
if(reto.Integer.Value != 0){ | |||||
return (EINVAL); | |||||
} | |||||
return (0); | |||||
} | |||||
static void acpi_rtc_notify_handler(ACPI_HANDLE h, UINT32 notify, void *ctx) | |||||
{ | |||||
#if 0 | |||||
device_t dev = (device_t) ctx; | |||||
device_printf(dev, "Notify %d\n"); | |||||
#endif | |||||
acpi_UserNotify("RTCWAKE", h, notify); | |||||
} | |||||
static int acpi_rtc_probe(device_t dev) | |||||
{ | |||||
int ret; | |||||
ret = ACPI_ID_PROBE(device_get_parent(dev), dev, acpi_rtc_id, NULL); | |||||
if (ret <= 0) { | |||||
device_set_desc(dev, "Date and Alarm Device"); | |||||
} | |||||
return (ret); | |||||
} | |||||
static int acpi_rtc_wakestate(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
device_t dev ; | |||||
int arg, val; | |||||
ACPI_STATUS status; | |||||
int error; | |||||
dev = (device_t) oidp->oid_arg1; | |||||
arg = oidp->oid_arg2; | |||||
if (ACPI_FAILURE(acpi_rtc_arg1_call(dev, "_GWS", arg, &val))) | |||||
return EINVAL; | |||||
error = sysctl_handle_int(oidp, &val, 0,req); | |||||
if ( error || ! req->newptr) | |||||
return error; | |||||
status = acpi_rtc_arg1_call(dev, "_CWS", arg, &val); | |||||
if(ACPI_FAILURE(status) || (val != 0)) | |||||
return EINVAL; | |||||
return 0; | |||||
} | |||||
static int acpi_rtc_timer(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
device_t dev ; | |||||
int arg, val; | |||||
ACPI_STATUS status; | |||||
int error; | |||||
dev = (device_t) oidp->oid_arg1; | |||||
arg = oidp->oid_arg2; | |||||
if (ACPI_FAILURE(acpi_rtc_arg1_call(dev, "_TIV", arg, &val))) | |||||
return EINVAL; | |||||
error = sysctl_handle_int(oidp, &val, 0,req); | |||||
if ( error || ! req->newptr) | |||||
return error; | |||||
status = acpi_rtc_arg2_call(dev, "_STV", arg, val, &val); | |||||
if(ACPI_FAILURE(status) || (val != 0)) | |||||
return EINVAL; | |||||
return 0; | |||||
} | |||||
static int acpi_rtc_policy(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
device_t dev ; | |||||
int arg, val; | |||||
ACPI_STATUS status; | |||||
int error; | |||||
dev = (device_t) oidp->oid_arg1; | |||||
arg = oidp->oid_arg2; | |||||
if (ACPI_FAILURE(acpi_rtc_arg1_call(dev, "_TIP", arg, &val))) | |||||
return EINVAL; | |||||
error = sysctl_handle_int(oidp, &val, 0,req); | |||||
if ( error || ! req->newptr) | |||||
return error; | |||||
status = acpi_rtc_arg2_call(dev, "_STP", arg, val, &val); | |||||
if(ACPI_FAILURE(status) || (val != 0)) | |||||
return EINVAL; | |||||
return 0; | |||||
} | |||||
static int acpi_rtc_attach(device_t dev) | |||||
{ | |||||
struct acpi_rtc_softc *sc = device_get_softc(dev); | |||||
struct acpi_rtc_ct act; | |||||
struct sysctl_ctx_list *sysctl_ctx = device_get_sysctl_ctx(dev); | |||||
struct sysctl_oid *sysctl_tree = device_get_sysctl_tree(dev); | |||||
if(ACPI_FAILURE(acpi_GetInteger(acpi_get_handle(dev), "_GCP", &sc->device_cap))){ | |||||
return ENXIO; | |||||
} | |||||
device_printf(dev, "Capacity %b\n", sc->device_cap, "\020\1AC\2DC\3RTC\4mS\5GWSS45\6S4AC\7S5AC\010S4DC\011S5DC"); | |||||
if(sc->device_cap & ACPI_RTC_RTC){ | |||||
acpi_rtc_grt(dev, &act); | |||||
sc->tzmin = act.TimeZone; | |||||
if(act.Daylight) | |||||
device_printf (dev, "Warning: Daylight time affect the value\n"); | |||||
clock_register_flags(dev, | |||||
(sc->device_cap & ACPI_RTC_RTC_MSEC) ? 1000 : 1000000, | |||||
(sc->tzmin == ACPI_RTC_TZ_UNSPEC) ? 0 : | |||||
(CLOCKF_GETTIME_NO_ADJ | CLOCKF_SETTIME_NO_ADJ)); | |||||
} | |||||
AcpiInstallNotifyHandler(acpi_get_handle(dev), | |||||
ACPI_ALL_NOTIFY, | |||||
acpi_rtc_notify_handler, dev); | |||||
acpi_wake_set_enable(dev, 1); | |||||
SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), | |||||
OID_AUTO, "ac_wake_status", | |||||
CTLTYPE_INT | CTLFLAG_RW |CTLFLAG_MPSAFE, | |||||
dev, 0, acpi_rtc_wakestate, "I", | |||||
"AC Wake status. write any value to clear"); | |||||
SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), | |||||
OID_AUTO, "dc_wake_status", | |||||
CTLTYPE_INT | CTLFLAG_RW |CTLFLAG_MPSAFE, | |||||
dev, 1, acpi_rtc_wakestate, "I", | |||||
"DC Wake status. write any value to clear"); | |||||
SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), | |||||
OID_AUTO, "ac_wake_timer", | |||||
CTLTYPE_INT | CTLFLAG_RW |CTLFLAG_MPSAFE, | |||||
dev, 0, acpi_rtc_timer, "I", | |||||
"AC Wake count down timer in second. -1 means timer expired"); | |||||
SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), | |||||
OID_AUTO, "dc_wake_timer", | |||||
CTLTYPE_INT | CTLFLAG_RW |CTLFLAG_MPSAFE, | |||||
dev, 1, acpi_rtc_timer, "I", | |||||
"DC Wake count down timer in second. -1 means timer expired."); | |||||
SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), | |||||
OID_AUTO, "ac_wake_policy", | |||||
CTLTYPE_INT | CTLFLAG_RW |CTLFLAG_MPSAFE, | |||||
dev, 0, acpi_rtc_policy, "I", | |||||
"AC Wake policy"); | |||||
SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), | |||||
OID_AUTO, "dc_wake_policy", | |||||
CTLTYPE_INT | CTLFLAG_RW |CTLFLAG_MPSAFE, | |||||
dev, 1, acpi_rtc_policy, "I", | |||||
"DC Wake policy"); | |||||
return 0; | |||||
} | |||||
static int acpi_rtc_detach(device_t dev) | |||||
{ | |||||
AcpiRemoveNotifyHandler(acpi_get_handle(dev), | |||||
ACPI_ALL_NOTIFY, | |||||
acpi_rtc_notify_handler); | |||||
return 0; | |||||
} | |||||
static device_method_t acpi_rtc_methods[] = { | |||||
/* Device interface */ | |||||
DEVMETHOD(device_probe, acpi_rtc_probe), | |||||
DEVMETHOD(device_attach, acpi_rtc_attach), | |||||
DEVMETHOD(device_detach, acpi_rtc_detach), | |||||
DEVMETHOD(clock_gettime, acpi_rtc_gettime), | |||||
DEVMETHOD(clock_settime, acpi_rtc_settime), | |||||
DEVMETHOD_END | |||||
}; | |||||
static driver_t acpi_rtc_driver = { | |||||
"acpi_rtc", | |||||
acpi_rtc_methods, | |||||
sizeof(struct acpi_rtc_softc), | |||||
}; | |||||
devclass_t acpi_rtc_devclass; | |||||
DRIVER_MODULE(acpi_rtc, acpi, acpi_rtc_driver, acpi_rtc_devclass, 0, 0); | |||||
MODULE_DEPEND(acpi_rtc, acpi, 1, 1, 1); |