1*eb720dcaSkettenis /* $OpenBSD: aplsmc.c,v 1.28 2024/11/04 09:33:16 kettenis Exp $ */ 2d80548acSkettenis /* 3d80548acSkettenis * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org> 4d80548acSkettenis * 5d80548acSkettenis * Permission to use, copy, modify, and distribute this software for any 6d80548acSkettenis * purpose with or without fee is hereby granted, provided that the above 7d80548acSkettenis * copyright notice and this permission notice appear in all copies. 8d80548acSkettenis * 9d80548acSkettenis * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10d80548acSkettenis * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11d80548acSkettenis * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12d80548acSkettenis * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13d80548acSkettenis * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14d80548acSkettenis * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15d80548acSkettenis * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16d80548acSkettenis */ 17d80548acSkettenis 18d80548acSkettenis #include <sys/param.h> 19d80548acSkettenis #include <sys/systm.h> 20d80548acSkettenis #include <sys/device.h> 21dc827a26Skettenis #include <sys/proc.h> 22d80548acSkettenis #include <sys/sensors.h> 23dc827a26Skettenis #include <sys/signalvar.h> 24d80548acSkettenis 257171b1efSrobert #include <machine/apmvar.h> 26d80548acSkettenis #include <machine/bus.h> 27d80548acSkettenis #include <machine/fdt.h> 28d80548acSkettenis 2929e9a116Skettenis #include <dev/clock_subr.h> 30d80548acSkettenis #include <dev/ofw/openfirm.h> 31c99a06d7Skettenis #include <dev/ofw/ofw_gpio.h> 32d80548acSkettenis #include <dev/ofw/ofw_misc.h> 33d80548acSkettenis #include <dev/ofw/fdt.h> 34d80548acSkettenis 35d80548acSkettenis #include <arm64/dev/aplmbox.h> 36d80548acSkettenis #include <arm64/dev/rtkit.h> 37d80548acSkettenis 387171b1efSrobert #include "apm.h" 397171b1efSrobert 40ec9fe3b7Stobhe extern int lid_action; 41c4d8daa7Stobhe extern void (*simplefb_burn_hook)(u_int); 42c4d8daa7Stobhe 43a5505455Skettenis extern void (*cpuresetfn)(void); 4457f9f74cSkettenis extern void (*powerdownfn)(void); 45a5505455Skettenis 465acccd09Skettenis extern int (*hw_battery_setchargemode)(int); 475acccd09Skettenis extern int (*hw_battery_setchargestart)(int); 485acccd09Skettenis extern int (*hw_battery_setchargestop)(int); 495acccd09Skettenis extern int hw_battery_chargemode; 505acccd09Skettenis extern int hw_battery_chargestart; 515acccd09Skettenis extern int hw_battery_chargestop; 525acccd09Skettenis 5340fc16d4Skettenis /* SMC mailbox endpoint */ 54d80548acSkettenis #define SMC_EP 32 55d80548acSkettenis 5640fc16d4Skettenis /* SMC commands */ 57dc827a26Skettenis #define SMC_CMD(d) ((d) & 0xff) 58d80548acSkettenis #define SMC_READ_KEY 0x10 59d80548acSkettenis #define SMC_WRITE_KEY 0x11 60d80548acSkettenis #define SMC_GET_KEY_BY_INDEX 0x12 61d80548acSkettenis #define SMC_GET_KEY_INFO 0x13 62d80548acSkettenis #define SMC_GET_SRAM_ADDR 0x17 6340fc16d4Skettenis #define SMC_SRAM_SIZE 0x4000 64dc827a26Skettenis #define SMC_NOTIFICATION 0x18 65d80548acSkettenis 6640fc16d4Skettenis /* SMC errors */ 67d80548acSkettenis #define SMC_ERROR(d) ((d) & 0xff) 68d80548acSkettenis #define SMC_OK 0x00 69d80548acSkettenis #define SMC_KEYNOTFOUND 0x84 70d80548acSkettenis 7140fc16d4Skettenis /* SMC keys */ 72d80548acSkettenis #define SMC_KEY(s) ((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3]) 73d80548acSkettenis 74d80548acSkettenis struct smc_key_info { 75d80548acSkettenis uint8_t size; 76d80548acSkettenis uint8_t type[4]; 77d80548acSkettenis uint8_t flags; 78d80548acSkettenis }; 79d80548acSkettenis 8040fc16d4Skettenis /* SMC GPIO commands */ 8140fc16d4Skettenis #define SMC_GPIO_CMD_OUTPUT (0x01 << 24) 8240fc16d4Skettenis 8340fc16d4Skettenis /* RTC related constants */ 8440fc16d4Skettenis #define RTC_OFFSET_LEN 6 8540fc16d4Skettenis #define SMC_CLKM_LEN 6 8640fc16d4Skettenis 87d80548acSkettenis struct aplsmc_sensor { 88d80548acSkettenis const char *key; 89d80548acSkettenis const char *key_type; 90d80548acSkettenis enum sensor_type type; 91d80548acSkettenis int scale; 92d80548acSkettenis const char *desc; 93d80548acSkettenis int flags; 94d80548acSkettenis }; 95d80548acSkettenis 96dc827a26Skettenis /* SMC events */ 97dc827a26Skettenis #define SMC_EV_TYPE(d) (((d) >> 48) & 0xffff) 98dc827a26Skettenis #define SMC_EV_TYPE_BTN 0x7201 99dc827a26Skettenis #define SMC_EV_TYPE_LID 0x7203 100dc827a26Skettenis #define SMC_EV_SUBTYPE(d) (((d) >> 40) & 0xff) 101dc827a26Skettenis #define SMC_EV_DATA(d) (((d) >> 32) & 0xff) 102dc827a26Skettenis 103dc827a26Skettenis /* Button events */ 104dc827a26Skettenis #define SMC_PWRBTN_OFF 0x00 10521a34467Skettenis #define SMC_PWRBTN_SHORT 0x01 10621a34467Skettenis #define SMC_PWRBTN_TOUCHID 0x06 107dc827a26Skettenis #define SMC_PWRBTN_LONG 0xfe 108dc827a26Skettenis 109c4d8daa7Stobhe /* Lid events */ 110c4d8daa7Stobhe #define SMC_LID_OPEN 0x00 111c4d8daa7Stobhe #define SMC_LID_CLOSE 0x01 112c4d8daa7Stobhe 113d80548acSkettenis #define APLSMC_BE (1 << 0) 1147171b1efSrobert #define APLSMC_HIDDEN (1 << 1) 115d80548acSkettenis 1167171b1efSrobert #define APLSMC_MAX_SENSORS 19 117d80548acSkettenis 118d80548acSkettenis struct aplsmc_softc { 119d80548acSkettenis struct device sc_dev; 120d80548acSkettenis bus_space_tag_t sc_iot; 121d80548acSkettenis bus_space_handle_t sc_ioh; 122d80548acSkettenis bus_space_handle_t sc_sram_ioh; 123d80548acSkettenis 124d80548acSkettenis void *sc_ih; 125d80548acSkettenis 126d80548acSkettenis struct rtkit_state *sc_rs; 127d80548acSkettenis uint8_t sc_msgid; 128d80548acSkettenis uint64_t sc_data; 129d80548acSkettenis 130c99a06d7Skettenis struct gpio_controller sc_gc; 131c99a06d7Skettenis 13229e9a116Skettenis int sc_rtc_node; 13329e9a116Skettenis struct todr_chip_handle sc_todr; 13429e9a116Skettenis 13557f9f74cSkettenis int sc_reboot_node; 13657f9f74cSkettenis 137d80548acSkettenis struct aplsmc_sensor *sc_smcsensors[APLSMC_MAX_SENSORS]; 138d80548acSkettenis struct ksensor sc_sensors[APLSMC_MAX_SENSORS]; 139d80548acSkettenis int sc_nsensors; 140d80548acSkettenis struct ksensordev sc_sensordev; 141605e6c2aSkettenis uint32_t sc_suspend_pstr; 142a5505455Skettenis }; 143a5505455Skettenis 1445acccd09Skettenis #define CH0I_DISCHARGE (1 << 0) 1455acccd09Skettenis #define CH0C_INHIBIT (1 << 0) 146*eb720dcaSkettenis #define CHLS_FORCE_DISCHARGE (1 << 8) 1475acccd09Skettenis 148a5505455Skettenis struct aplsmc_softc *aplsmc_sc; 149d80548acSkettenis 15029e9a116Skettenis #ifndef SMALL_KERNEL 15129e9a116Skettenis 152d80548acSkettenis struct aplsmc_sensor aplsmc_sensors[] = { 1537171b1efSrobert { "ACDI", "ui16", SENSOR_INDICATOR, 1, "power supply" }, 154d80548acSkettenis { "B0RM", "ui16", SENSOR_AMPHOUR, 1000, "remaining battery capacity", 155d80548acSkettenis APLSMC_BE }, 156d80548acSkettenis { "B0FC", "ui16", SENSOR_AMPHOUR, 1000, "last full battery capacity" }, 157d80548acSkettenis { "B0DC", "ui16", SENSOR_AMPHOUR, 1000, "battery design capacity" }, 158d80548acSkettenis { "B0AV", "ui16", SENSOR_VOLTS_DC, 1000, "battery" }, 159d80548acSkettenis { "B0CT", "ui16", SENSOR_INTEGER, 1, "battery discharge cycles" }, 1607171b1efSrobert { "B0TF", "ui16", SENSOR_INTEGER, 1, "battery time-to-full", 1617171b1efSrobert APLSMC_HIDDEN }, 1627171b1efSrobert { "B0TE", "ui16", SENSOR_INTEGER, 1, "battery time-to-empty", 1637171b1efSrobert APLSMC_HIDDEN }, 164d80548acSkettenis { "F0Ac", "flt ", SENSOR_FANRPM, 1, "" }, 165d80548acSkettenis { "ID0R", "flt ", SENSOR_AMPS, 1000000, "input" }, 166d80548acSkettenis { "PDTR", "flt ", SENSOR_WATTS, 1000000, "input" }, 167d80548acSkettenis { "PSTR", "flt ", SENSOR_WATTS, 1000000, "system" }, 168d80548acSkettenis { "TB0T", "flt ", SENSOR_TEMP, 1000000, "battery" }, 169d80548acSkettenis { "TCHP", "flt ", SENSOR_TEMP, 1000000, "charger" }, 170d80548acSkettenis { "TW0P", "flt ", SENSOR_TEMP, 1000000, "wireless" }, 171d80548acSkettenis { "Ts0P", "flt ", SENSOR_TEMP, 1000000, "palm rest" }, 172d80548acSkettenis { "Ts1P", "flt ", SENSOR_TEMP, 1000000, "palm rest" }, 173d80548acSkettenis { "VD0R", "flt ", SENSOR_VOLTS_DC, 1000000, "input" }, 174d80548acSkettenis }; 175d80548acSkettenis 17629e9a116Skettenis #endif 17729e9a116Skettenis 178d80548acSkettenis int aplsmc_match(struct device *, void *, void *); 179d80548acSkettenis void aplsmc_attach(struct device *, struct device *, void *); 180605e6c2aSkettenis int aplsmc_activate(struct device *, int); 181d80548acSkettenis 182d80548acSkettenis const struct cfattach aplsmc_ca = { 183605e6c2aSkettenis sizeof (struct aplsmc_softc), aplsmc_match, aplsmc_attach, 184605e6c2aSkettenis NULL, aplsmc_activate 185d80548acSkettenis }; 186d80548acSkettenis 187d80548acSkettenis struct cfdriver aplsmc_cd = { 188d80548acSkettenis NULL, "aplsmc", DV_DULL 189d80548acSkettenis }; 190d80548acSkettenis 191d80548acSkettenis void aplsmc_callback(void *, uint64_t); 192d80548acSkettenis int aplsmc_send_cmd(struct aplsmc_softc *, uint16_t, uint32_t, uint16_t); 193d80548acSkettenis int aplsmc_wait_cmd(struct aplsmc_softc *sc); 194d80548acSkettenis int aplsmc_read_key(struct aplsmc_softc *, uint32_t, void *, size_t); 195dc827a26Skettenis int aplsmc_write_key(struct aplsmc_softc *, uint32_t, void *, size_t); 196605e6c2aSkettenis int64_t aplsmc_convert_flt(uint32_t, int); 197d80548acSkettenis void aplsmc_refresh_sensors(void *); 19829e9a116Skettenis int aplsmc_apminfo(struct apm_power_info *); 199c99a06d7Skettenis void aplsmc_set_pin(void *, uint32_t *, int); 20029e9a116Skettenis int aplsmc_gettime(struct todr_chip_handle *, struct timeval *); 20129e9a116Skettenis int aplsmc_settime(struct todr_chip_handle *, struct timeval *); 202a5505455Skettenis void aplsmc_reset(void); 20357f9f74cSkettenis void aplsmc_powerdown(void); 20457f9f74cSkettenis void aplsmc_reboot_attachhook(struct device *); 2055acccd09Skettenis void aplsmc_battery_init(struct aplsmc_softc *); 2065acccd09Skettenis int aplsmc_battery_setchargemode(int); 2075acccd09Skettenis int aplsmc_battery_setchargestart(int); 208*eb720dcaSkettenis int aplsmc_battery_chls_setchargestop(int); 209*eb720dcaSkettenis int aplsmc_battery_chwa_setchargestop(int); 210d80548acSkettenis 211d80548acSkettenis int 212d80548acSkettenis aplsmc_match(struct device *parent, void *match, void *aux) 213d80548acSkettenis { 214d80548acSkettenis struct fdt_attach_args *faa = aux; 215d80548acSkettenis 216d80548acSkettenis return OF_is_compatible(faa->fa_node, "apple,smc"); 217d80548acSkettenis } 218d80548acSkettenis 219d80548acSkettenis void 220d80548acSkettenis aplsmc_attach(struct device *parent, struct device *self, void *aux) 221d80548acSkettenis { 222d80548acSkettenis struct aplsmc_softc *sc = (struct aplsmc_softc *)self; 223d80548acSkettenis struct fdt_attach_args *faa = aux; 22440fc16d4Skettenis uint8_t data[SMC_CLKM_LEN]; 225dc827a26Skettenis uint8_t ntap; 226c99a06d7Skettenis int error, node; 22729e9a116Skettenis #ifndef SMALL_KERNEL 228d80548acSkettenis int i; 22929e9a116Skettenis #endif 230d80548acSkettenis 231d80548acSkettenis if (faa->fa_nreg < 1) { 232d80548acSkettenis printf(": no registers\n"); 233d80548acSkettenis return; 234d80548acSkettenis } 235d80548acSkettenis 236d80548acSkettenis sc->sc_iot = faa->fa_iot; 237d80548acSkettenis if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, 238d80548acSkettenis faa->fa_reg[0].size, 0, &sc->sc_ioh)) { 239d80548acSkettenis printf(": can't map registers\n"); 240d80548acSkettenis return; 241d80548acSkettenis } 242d80548acSkettenis 2433b6109e0Skettenis sc->sc_rs = rtkit_init(faa->fa_node, NULL, RK_WAKEUP, NULL); 244d80548acSkettenis if (sc->sc_rs == NULL) { 245d80548acSkettenis printf(": can't map mailbox channel\n"); 246d80548acSkettenis return; 247d80548acSkettenis } 248d80548acSkettenis 249d80548acSkettenis error = rtkit_boot(sc->sc_rs); 250d80548acSkettenis if (error) { 251d80548acSkettenis printf(": can't boot firmware\n"); 252d80548acSkettenis return; 253d80548acSkettenis } 254d80548acSkettenis 255d80548acSkettenis error = rtkit_start_endpoint(sc->sc_rs, SMC_EP, aplsmc_callback, sc); 256d80548acSkettenis if (error) { 257d80548acSkettenis printf(": can't start SMC endpoint\n"); 258d80548acSkettenis return; 259d80548acSkettenis } 260d80548acSkettenis 261d80548acSkettenis aplsmc_send_cmd(sc, SMC_GET_SRAM_ADDR, 0, 0); 262d80548acSkettenis error = aplsmc_wait_cmd(sc); 263d80548acSkettenis if (error) { 264d80548acSkettenis printf(": can't get SRAM address\n"); 265d80548acSkettenis return; 266d80548acSkettenis } 267d80548acSkettenis 268d80548acSkettenis if (bus_space_map(sc->sc_iot, sc->sc_data, 269d80548acSkettenis SMC_SRAM_SIZE, 0, &sc->sc_sram_ioh)) { 270d80548acSkettenis printf(": can't map SRAM\n"); 271d80548acSkettenis return; 272d80548acSkettenis } 273d80548acSkettenis 274d80548acSkettenis printf("\n"); 275d80548acSkettenis 27657f9f74cSkettenis aplsmc_sc = sc; 27757f9f74cSkettenis 278c99a06d7Skettenis node = OF_getnodebyname(faa->fa_node, "gpio"); 279c99a06d7Skettenis if (node) { 280c99a06d7Skettenis sc->sc_gc.gc_node = node; 281c99a06d7Skettenis sc->sc_gc.gc_cookie = sc; 282c99a06d7Skettenis sc->sc_gc.gc_set_pin = aplsmc_set_pin; 283c99a06d7Skettenis gpio_controller_register(&sc->sc_gc); 284c99a06d7Skettenis } 285c99a06d7Skettenis 28640fc16d4Skettenis /* 28740fc16d4Skettenis * Only provide TODR implementation if the "CLKM" key is 28840fc16d4Skettenis * supported by the SMC firmware. 28940fc16d4Skettenis */ 29040fc16d4Skettenis error = aplsmc_read_key(sc, SMC_KEY("CLKM"), &data, SMC_CLKM_LEN); 29129e9a116Skettenis node = OF_getnodebyname(faa->fa_node, "rtc"); 29240fc16d4Skettenis if (node && error == 0) { 29329e9a116Skettenis sc->sc_rtc_node = node; 29429e9a116Skettenis sc->sc_todr.cookie = sc; 29529e9a116Skettenis sc->sc_todr.todr_gettime = aplsmc_gettime; 29629e9a116Skettenis sc->sc_todr.todr_settime = aplsmc_settime; 2970701a158Skettenis sc->sc_todr.todr_quality = 1000; 29829e9a116Skettenis todr_attach(&sc->sc_todr); 29929e9a116Skettenis } 30029e9a116Skettenis 30157f9f74cSkettenis node = OF_getnodebyname(faa->fa_node, "reboot"); 30257f9f74cSkettenis if (node) { 30357f9f74cSkettenis sc->sc_reboot_node = node; 30429e9a116Skettenis cpuresetfn = aplsmc_reset; 30557f9f74cSkettenis powerdownfn = aplsmc_powerdown; 30657f9f74cSkettenis config_mountroot(self, aplsmc_reboot_attachhook); 30757f9f74cSkettenis } 30829e9a116Skettenis 309dc827a26Skettenis /* Enable notifications. */ 310dc827a26Skettenis ntap = 1; 311dc827a26Skettenis aplsmc_write_key(sc, SMC_KEY("NTAP"), &ntap, sizeof(ntap)); 312dc827a26Skettenis 31329e9a116Skettenis #ifndef SMALL_KERNEL 31429e9a116Skettenis 315d80548acSkettenis for (i = 0; i < nitems(aplsmc_sensors); i++) { 316d80548acSkettenis struct smc_key_info info; 317d80548acSkettenis 318d80548acSkettenis aplsmc_send_cmd(sc, SMC_GET_KEY_INFO, 319d80548acSkettenis SMC_KEY(aplsmc_sensors[i].key), 0); 320d80548acSkettenis error = aplsmc_wait_cmd(sc); 321d80548acSkettenis if (error || SMC_ERROR(sc->sc_data) != SMC_OK) 322d80548acSkettenis continue; 323d80548acSkettenis 324d80548acSkettenis bus_space_read_region_1(sc->sc_iot, sc->sc_sram_ioh, 0, 325d80548acSkettenis (uint8_t *)&info, sizeof(info)); 326d80548acSkettenis 327d80548acSkettenis /* Skip if the key type doesn't match. */ 328d80548acSkettenis if (memcmp(aplsmc_sensors[i].key_type, info.type, 329d80548acSkettenis sizeof(info.type)) != 0) 330d80548acSkettenis continue; 331d80548acSkettenis 332d80548acSkettenis if (sc->sc_nsensors >= APLSMC_MAX_SENSORS) { 333ef6635adSjsg printf("%s: maximum number of sensors exceeded\n", 334d80548acSkettenis sc->sc_dev.dv_xname); 335d80548acSkettenis break; 336d80548acSkettenis } 337d80548acSkettenis 338d80548acSkettenis sc->sc_smcsensors[sc->sc_nsensors] = &aplsmc_sensors[i]; 339d80548acSkettenis strlcpy(sc->sc_sensors[sc->sc_nsensors].desc, 340d80548acSkettenis aplsmc_sensors[i].desc, sizeof(sc->sc_sensors[0].desc)); 341d80548acSkettenis sc->sc_sensors[sc->sc_nsensors].type = aplsmc_sensors[i].type; 3427171b1efSrobert if (!(aplsmc_sensors[i].flags & APLSMC_HIDDEN)) { 343d80548acSkettenis sensor_attach(&sc->sc_sensordev, 344d80548acSkettenis &sc->sc_sensors[sc->sc_nsensors]); 3457171b1efSrobert } 346d80548acSkettenis sc->sc_nsensors++; 347d80548acSkettenis } 348d80548acSkettenis 349d80548acSkettenis aplsmc_refresh_sensors(sc); 350d80548acSkettenis 351d80548acSkettenis strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, 352d80548acSkettenis sizeof(sc->sc_sensordev.xname)); 353d80548acSkettenis sensordev_install(&sc->sc_sensordev); 354d80548acSkettenis sensor_task_register(sc, aplsmc_refresh_sensors, 5); 3557171b1efSrobert 3567171b1efSrobert #if NAPM > 0 3577171b1efSrobert apm_setinfohook(aplsmc_apminfo); 3587171b1efSrobert #endif 35929e9a116Skettenis 3605acccd09Skettenis aplsmc_battery_init(sc); 36129e9a116Skettenis #endif 3627bd61a10Skettenis 3637bd61a10Skettenis #ifdef SUSPEND 3647bd61a10Skettenis device_register_wakeup(&sc->sc_dev); 3657bd61a10Skettenis #endif 366605e6c2aSkettenis } 3675acccd09Skettenis 368605e6c2aSkettenis int 369605e6c2aSkettenis aplsmc_activate(struct device *self, int act) 370605e6c2aSkettenis { 371605e6c2aSkettenis #ifdef SUSPEND 372605e6c2aSkettenis struct aplsmc_softc *sc = (struct aplsmc_softc *)self; 373605e6c2aSkettenis int64_t value; 374605e6c2aSkettenis 375605e6c2aSkettenis switch (act) { 376605e6c2aSkettenis case DVACT_WAKEUP: 377605e6c2aSkettenis value = aplsmc_convert_flt(sc->sc_suspend_pstr, 100); 378605e6c2aSkettenis printf("%s: system %lld.%02lld W\n", sc->sc_dev.dv_xname, 379605e6c2aSkettenis value / 100, value % 100); 380605e6c2aSkettenis } 381605e6c2aSkettenis #endif 382605e6c2aSkettenis 383605e6c2aSkettenis return 0; 384d80548acSkettenis } 385d80548acSkettenis 386d80548acSkettenis void 387dc827a26Skettenis aplsmc_handle_notification(struct aplsmc_softc *sc, uint64_t data) 388dc827a26Skettenis { 389dc827a26Skettenis extern int allowpowerdown; 39000c72810Skettenis #ifdef SUSPEND 3917bd61a10Skettenis extern int cpu_suspended; 392605e6c2aSkettenis uint32_t flt = 0; 39300c72810Skettenis 39400c72810Skettenis if (cpu_suspended) { 395605e6c2aSkettenis aplsmc_read_key(sc, 'PSTR', &flt, sizeof(flt)); 396605e6c2aSkettenis sc->sc_suspend_pstr = flt; 397605e6c2aSkettenis 39800c72810Skettenis switch (SMC_EV_TYPE(data)) { 39900c72810Skettenis case SMC_EV_TYPE_BTN: 40000c72810Skettenis switch (SMC_EV_SUBTYPE(data)) { 40100c72810Skettenis case SMC_PWRBTN_SHORT: 40200c72810Skettenis case SMC_PWRBTN_TOUCHID: 40300c72810Skettenis cpu_suspended = 0; 40400c72810Skettenis break; 40500c72810Skettenis } 40600c72810Skettenis break; 40700c72810Skettenis case SMC_EV_TYPE_LID: 40800c72810Skettenis switch (SMC_EV_SUBTYPE(data)) { 40900c72810Skettenis case SMC_LID_OPEN: 41000c72810Skettenis cpu_suspended = 0; 41100c72810Skettenis break; 41200c72810Skettenis } 41300c72810Skettenis break; 41400c72810Skettenis } 41500c72810Skettenis 41600c72810Skettenis return; 41700c72810Skettenis } 41800c72810Skettenis #endif 419dc827a26Skettenis 420dc827a26Skettenis switch (SMC_EV_TYPE(data)) { 421dc827a26Skettenis case SMC_EV_TYPE_BTN: 422dc827a26Skettenis switch (SMC_EV_SUBTYPE(data)) { 423dc827a26Skettenis case SMC_PWRBTN_SHORT: 42421a34467Skettenis case SMC_PWRBTN_TOUCHID: 4257bd61a10Skettenis if (SMC_EV_DATA(data) == 1) { 42694abace8Skettenis if (allowpowerdown) { 427dc827a26Skettenis allowpowerdown = 0; 428dc827a26Skettenis prsignal(initprocess, SIGUSR2); 429dc827a26Skettenis } 4307bd61a10Skettenis } 431dc827a26Skettenis break; 432dc827a26Skettenis case SMC_PWRBTN_LONG: 433dc827a26Skettenis break; 434dc827a26Skettenis case SMC_PWRBTN_OFF: 435dc827a26Skettenis /* XXX Do emergency shutdown? */ 436dc827a26Skettenis break; 437dc827a26Skettenis default: 438dc827a26Skettenis printf("%s: SMV_EV_TYPE_BTN 0x%016llx\n", 439dc827a26Skettenis sc->sc_dev.dv_xname, data); 440dc827a26Skettenis break; 441dc827a26Skettenis } 442dc827a26Skettenis break; 443dc827a26Skettenis case SMC_EV_TYPE_LID: 44400c72810Skettenis switch (lid_action) { 44500c72810Skettenis case 0: 446c4d8daa7Stobhe switch (SMC_EV_SUBTYPE(data)) { 447c4d8daa7Stobhe case SMC_LID_OPEN: 448c4d8daa7Stobhe if (simplefb_burn_hook) 449c4d8daa7Stobhe simplefb_burn_hook(1); 450c4d8daa7Stobhe break; 451c4d8daa7Stobhe case SMC_LID_CLOSE: 452c4d8daa7Stobhe if (simplefb_burn_hook) 453c4d8daa7Stobhe simplefb_burn_hook(0); 454c4d8daa7Stobhe break; 455c4d8daa7Stobhe default: 456c4d8daa7Stobhe printf("%s: SMV_EV_TYPE_LID 0x%016llx\n", 457c4d8daa7Stobhe sc->sc_dev.dv_xname, data); 458c4d8daa7Stobhe break; 459c4d8daa7Stobhe } 460ec9fe3b7Stobhe case 1: 46100c72810Skettenis #ifdef SUSPEND 4628cae9a59Stobhe request_sleep(SLEEP_SUSPEND); 46300c72810Skettenis #endif 464ec9fe3b7Stobhe break; 465ec9fe3b7Stobhe case 2: 466ec9fe3b7Stobhe /* XXX: hibernate */ 467ec9fe3b7Stobhe break; 468ec9fe3b7Stobhe } 469dc827a26Skettenis break; 470dc827a26Skettenis default: 471dc827a26Skettenis #ifdef APLSMC_DEBUG 472dc827a26Skettenis printf("%s: unhandled event 0x%016llx\n", 473dc827a26Skettenis sc->sc_dev.dv_xname, data); 474dc827a26Skettenis #endif 475dc827a26Skettenis break; 476dc827a26Skettenis } 477dc827a26Skettenis } 478dc827a26Skettenis 479dc827a26Skettenis void 480d80548acSkettenis aplsmc_callback(void *arg, uint64_t data) 481d80548acSkettenis { 482d80548acSkettenis struct aplsmc_softc *sc = arg; 483d80548acSkettenis 484dc827a26Skettenis if (SMC_CMD(data) == SMC_NOTIFICATION) { 485dc827a26Skettenis aplsmc_handle_notification(sc, data); 486dc827a26Skettenis return; 487dc827a26Skettenis } 488dc827a26Skettenis 489d80548acSkettenis sc->sc_data = data; 490d80548acSkettenis wakeup(&sc->sc_data); 491d80548acSkettenis } 492d80548acSkettenis 493d80548acSkettenis int 494d80548acSkettenis aplsmc_send_cmd(struct aplsmc_softc *sc, uint16_t cmd, uint32_t key, 495d80548acSkettenis uint16_t len) 496d80548acSkettenis { 497d80548acSkettenis uint64_t data; 498d80548acSkettenis 499d80548acSkettenis data = cmd; 500d80548acSkettenis data |= (uint64_t)len << 16; 501d80548acSkettenis data |= (uint64_t)key << 32; 502d80548acSkettenis data |= (sc->sc_msgid++ & 0xf) << 12; 503d80548acSkettenis 504d80548acSkettenis return rtkit_send_endpoint(sc->sc_rs, SMC_EP, data); 505d80548acSkettenis } 506d80548acSkettenis 507d80548acSkettenis int 508d80548acSkettenis aplsmc_wait_cmd(struct aplsmc_softc *sc) 509d80548acSkettenis { 510d80548acSkettenis if (cold) { 511d80548acSkettenis int error, timo; 512d80548acSkettenis 513d80548acSkettenis /* Poll for completion. */ 514d80548acSkettenis for (timo = 1000; timo > 0; timo--) { 515d80548acSkettenis error = rtkit_poll(sc->sc_rs); 516d80548acSkettenis if (error == 0) 517d80548acSkettenis return 0; 518d80548acSkettenis delay(10); 519d80548acSkettenis } 520d80548acSkettenis 521d80548acSkettenis return EWOULDBLOCK; 522d80548acSkettenis } 523d80548acSkettenis 524d80548acSkettenis /* Sleep until the callback wakes us up. */ 525d80548acSkettenis return tsleep_nsec(&sc->sc_data, PWAIT, "apsmc", 10000000); 526d80548acSkettenis } 527d80548acSkettenis 528d80548acSkettenis int 529d80548acSkettenis aplsmc_read_key(struct aplsmc_softc *sc, uint32_t key, void *data, size_t len) 530d80548acSkettenis { 531d80548acSkettenis int error; 532d80548acSkettenis 533d80548acSkettenis aplsmc_send_cmd(sc, SMC_READ_KEY, key, len); 534d80548acSkettenis error = aplsmc_wait_cmd(sc); 535d80548acSkettenis if (error) 536d80548acSkettenis return error; 53740fc16d4Skettenis switch (SMC_ERROR(sc->sc_data)) { 53840fc16d4Skettenis case SMC_OK: 53940fc16d4Skettenis break; 54040fc16d4Skettenis case SMC_KEYNOTFOUND: 54140fc16d4Skettenis return EINVAL; 54240fc16d4Skettenis break; 54340fc16d4Skettenis default: 54440fc16d4Skettenis return EIO; 54540fc16d4Skettenis break; 54640fc16d4Skettenis } 547d80548acSkettenis 548d80548acSkettenis len = min(len, (sc->sc_data >> 16) & 0xffff); 549d80548acSkettenis if (len > sizeof(uint32_t)) { 550d80548acSkettenis bus_space_read_region_1(sc->sc_iot, sc->sc_sram_ioh, 0, 551d80548acSkettenis data, len); 552d80548acSkettenis } else { 553d80548acSkettenis uint32_t tmp = (sc->sc_data >> 32); 554d80548acSkettenis memcpy(data, &tmp, len); 555d80548acSkettenis } 556d80548acSkettenis 557d80548acSkettenis return 0; 558d80548acSkettenis } 559d80548acSkettenis 560c99a06d7Skettenis int 561c99a06d7Skettenis aplsmc_write_key(struct aplsmc_softc *sc, uint32_t key, void *data, size_t len) 562c99a06d7Skettenis { 563c99a06d7Skettenis bus_space_write_region_1(sc->sc_iot, sc->sc_sram_ioh, 0, data, len); 564c99a06d7Skettenis bus_space_barrier(sc->sc_iot, sc->sc_sram_ioh, 0, len, 565c99a06d7Skettenis BUS_SPACE_BARRIER_WRITE); 566c99a06d7Skettenis aplsmc_send_cmd(sc, SMC_WRITE_KEY, key, len); 567c99a06d7Skettenis return aplsmc_wait_cmd(sc); 568c99a06d7Skettenis } 569c99a06d7Skettenis 57029e9a116Skettenis #ifndef SMALL_KERNEL 57129e9a116Skettenis 572605e6c2aSkettenis int64_t 573605e6c2aSkettenis aplsmc_convert_flt(uint32_t flt, int scale) 574605e6c2aSkettenis { 575605e6c2aSkettenis int64_t mant; 576605e6c2aSkettenis int sign, exp; 577605e6c2aSkettenis 578605e6c2aSkettenis /* 579605e6c2aSkettenis * Convert floating-point to integer, trying to keep as much 580605e6c2aSkettenis * resolution as possible given the scaling factor. 581605e6c2aSkettenis */ 582605e6c2aSkettenis sign = (flt >> 31) ? -1 : 1; 583605e6c2aSkettenis exp = ((flt >> 23) & 0xff) - 127; 584605e6c2aSkettenis mant = (flt & 0x7fffff) | 0x800000; 585605e6c2aSkettenis mant *= scale; 586605e6c2aSkettenis if (exp < 23) 587605e6c2aSkettenis return sign * (mant >> (23 - exp)); 588605e6c2aSkettenis else 589605e6c2aSkettenis return sign * (mant << (exp - 23)); 590605e6c2aSkettenis } 591605e6c2aSkettenis 592d80548acSkettenis void 593d80548acSkettenis aplsmc_refresh_sensors(void *arg) 594d80548acSkettenis { 595240c1448Srobert extern int hw_power; 596d80548acSkettenis struct aplsmc_softc *sc = arg; 597d80548acSkettenis struct aplsmc_sensor *sensor; 598d80548acSkettenis int64_t value; 599d80548acSkettenis uint32_t key; 600d80548acSkettenis int i, error; 601d80548acSkettenis 602d80548acSkettenis for (i = 0; i < sc->sc_nsensors; i++) { 603d80548acSkettenis sensor = sc->sc_smcsensors[i]; 604d80548acSkettenis key = SMC_KEY(sensor->key); 605d80548acSkettenis 606d80548acSkettenis if (strcmp(sensor->key_type, "ui8 ") == 0) { 607d80548acSkettenis uint8_t ui8; 608d80548acSkettenis 609d80548acSkettenis error = aplsmc_read_key(sc, key, &ui8, sizeof(ui8)); 610d80548acSkettenis value = (int64_t)ui8 * sensor->scale; 611d80548acSkettenis } else if (strcmp(sensor->key_type, "ui16") == 0) { 612d80548acSkettenis uint16_t ui16; 613d80548acSkettenis 614d80548acSkettenis error = aplsmc_read_key(sc, key, &ui16, sizeof(ui16)); 615d80548acSkettenis if (sensor->flags & APLSMC_BE) 616d80548acSkettenis ui16 = betoh16(ui16); 617d80548acSkettenis value = (int64_t)ui16 * sensor->scale; 618d80548acSkettenis } else if (strcmp(sensor->key_type, "flt ") == 0) { 619d80548acSkettenis uint32_t flt; 620d80548acSkettenis 621d80548acSkettenis error = aplsmc_read_key(sc, key, &flt, sizeof(flt)); 622d80548acSkettenis if (sensor->flags & APLSMC_BE) 623d80548acSkettenis flt = betoh32(flt); 624605e6c2aSkettenis value = aplsmc_convert_flt(flt, sensor->scale); 625d80548acSkettenis } 626d80548acSkettenis 627d80548acSkettenis /* Apple reports temperatures in degC. */ 628d80548acSkettenis if (sensor->type == SENSOR_TEMP) 629d80548acSkettenis value += 273150000; 630d80548acSkettenis 631d80548acSkettenis if (error) { 632a761f9adSkettenis sc->sc_sensors[i].flags |= SENSOR_FUNKNOWN; 633d80548acSkettenis } else { 634d80548acSkettenis sc->sc_sensors[i].flags &= ~SENSOR_FUNKNOWN; 635d80548acSkettenis sc->sc_sensors[i].value = value; 636d80548acSkettenis } 637240c1448Srobert 638240c1448Srobert if (strcmp(sensor->key, "ACDI") == 0) 639240c1448Srobert hw_power = (value > 0); 640d80548acSkettenis } 641d80548acSkettenis } 642a5505455Skettenis 64329e9a116Skettenis #if NAPM > 0 64429e9a116Skettenis 64529e9a116Skettenis int 64629e9a116Skettenis aplsmc_apminfo(struct apm_power_info *info) 64729e9a116Skettenis { 64829e9a116Skettenis struct aplsmc_sensor *sensor; 64929e9a116Skettenis struct ksensor *ksensor; 65029e9a116Skettenis struct aplsmc_softc *sc = aplsmc_sc; 65129e9a116Skettenis int remaining = -1, capacity = -1, i; 65229e9a116Skettenis 65329e9a116Skettenis info->battery_state = APM_BATT_UNKNOWN; 65429e9a116Skettenis info->ac_state = APM_AC_UNKNOWN; 65529e9a116Skettenis info->battery_life = 0; 65629e9a116Skettenis info->minutes_left = -1; 65729e9a116Skettenis 65829e9a116Skettenis for (i = 0; i < sc->sc_nsensors; i++) { 65929e9a116Skettenis sensor = sc->sc_smcsensors[i]; 66029e9a116Skettenis ksensor = &sc->sc_sensors[i]; 66129e9a116Skettenis 66229e9a116Skettenis if (ksensor->flags & SENSOR_FUNKNOWN) 66329e9a116Skettenis continue; 66429e9a116Skettenis 66529e9a116Skettenis if (strcmp(sensor->key, "ACDI") == 0) { 66629e9a116Skettenis info->ac_state = ksensor->value ? 66729e9a116Skettenis APM_AC_ON : APM_AC_OFF; 66829e9a116Skettenis } else if (strcmp(sensor->key, "B0RM") == 0) 66929e9a116Skettenis remaining = ksensor->value; 67029e9a116Skettenis else if (strcmp(sensor->key, "B0FC") == 0) 67129e9a116Skettenis capacity = ksensor->value; 67229e9a116Skettenis else if ((strcmp(sensor->key, "B0TE") == 0) && 67329e9a116Skettenis (ksensor->value != 0xffff)) 67429e9a116Skettenis info->minutes_left = ksensor->value; 67529e9a116Skettenis else if ((strcmp(sensor->key, "B0TF") == 0) && 67629e9a116Skettenis (ksensor->value != 0xffff)) { 67729e9a116Skettenis info->battery_state = APM_BATT_CHARGING; 67829e9a116Skettenis info->minutes_left = ksensor->value; 67929e9a116Skettenis } 68029e9a116Skettenis } 68129e9a116Skettenis 68229e9a116Skettenis /* calculate remaining battery if we have sane values */ 68329e9a116Skettenis if (remaining > -1 && capacity > 0) { 68429e9a116Skettenis info->battery_life = ((remaining * 100) / capacity); 68529e9a116Skettenis if (info->battery_state != APM_BATT_CHARGING) { 68629e9a116Skettenis if (info->battery_life > 50) 68729e9a116Skettenis info->battery_state = APM_BATT_HIGH; 68829e9a116Skettenis else if (info->battery_life > 25) 68929e9a116Skettenis info->battery_state = APM_BATT_LOW; 69029e9a116Skettenis else 69129e9a116Skettenis info->battery_state = APM_BATT_CRITICAL; 69229e9a116Skettenis } 69329e9a116Skettenis } 69429e9a116Skettenis 69529e9a116Skettenis return 0; 69629e9a116Skettenis } 69729e9a116Skettenis 69829e9a116Skettenis #endif 69929e9a116Skettenis #endif 70029e9a116Skettenis 701a5505455Skettenis void 702c99a06d7Skettenis aplsmc_set_pin(void *cookie, uint32_t *cells, int val) 703c99a06d7Skettenis { 704c99a06d7Skettenis struct aplsmc_softc *sc = cookie; 705c99a06d7Skettenis static char *digits = "0123456789abcdef"; 706c99a06d7Skettenis uint32_t pin = cells[0]; 707c99a06d7Skettenis uint32_t flags = cells[1]; 708c99a06d7Skettenis uint32_t key = SMC_KEY("gP\0\0"); 709c99a06d7Skettenis uint32_t data; 710c99a06d7Skettenis 711c99a06d7Skettenis KASSERT(pin < 256); 712c99a06d7Skettenis 713c99a06d7Skettenis key |= (digits[(pin >> 0) & 0xf] << 0); 7143f8f2130Skettenis key |= (digits[(pin >> 4) & 0xf] << 8); 715c99a06d7Skettenis 716c99a06d7Skettenis if (flags & GPIO_ACTIVE_LOW) 717c99a06d7Skettenis val = !val; 718c99a06d7Skettenis data = SMC_GPIO_CMD_OUTPUT | !!val; 719c99a06d7Skettenis 720c99a06d7Skettenis aplsmc_write_key(sc, key, &data, sizeof(data)); 721c99a06d7Skettenis } 722c99a06d7Skettenis 72329e9a116Skettenis int 72429e9a116Skettenis aplsmc_gettime(struct todr_chip_handle *handle, struct timeval *tv) 72529e9a116Skettenis { 72629e9a116Skettenis struct aplsmc_softc *sc = handle->cookie; 72729e9a116Skettenis uint8_t data[8] = {}; 72829e9a116Skettenis uint64_t offset, time; 72929e9a116Skettenis int error; 73029e9a116Skettenis 73129e9a116Skettenis error = nvmem_read_cell(sc->sc_rtc_node, "rtc_offset", &data, 73229e9a116Skettenis RTC_OFFSET_LEN); 73329e9a116Skettenis if (error) 73429e9a116Skettenis return error; 73529e9a116Skettenis offset = lemtoh64(data); 73629e9a116Skettenis 73729e9a116Skettenis error = aplsmc_read_key(sc, SMC_KEY("CLKM"), &data, SMC_CLKM_LEN); 73829e9a116Skettenis if (error) 73929e9a116Skettenis return error; 74029e9a116Skettenis time = lemtoh64(data) + offset; 74129e9a116Skettenis 74229e9a116Skettenis tv->tv_sec = (time >> 15); 74329e9a116Skettenis tv->tv_usec = (((time & 0x7fff) * 1000000) >> 15); 74429e9a116Skettenis return 0; 74529e9a116Skettenis } 74629e9a116Skettenis 74729e9a116Skettenis int 74829e9a116Skettenis aplsmc_settime(struct todr_chip_handle *handle, struct timeval *tv) 74929e9a116Skettenis { 75029e9a116Skettenis struct aplsmc_softc *sc = handle->cookie; 75129e9a116Skettenis uint8_t data[8] = {}; 75229e9a116Skettenis uint64_t offset, time; 75329e9a116Skettenis int error; 75429e9a116Skettenis 75529e9a116Skettenis error = aplsmc_read_key(sc, SMC_KEY("CLKM"), &data, SMC_CLKM_LEN); 75629e9a116Skettenis if (error) 75729e9a116Skettenis return error; 75829e9a116Skettenis 75929e9a116Skettenis time = ((uint64_t)tv->tv_sec << 15); 76029e9a116Skettenis time |= ((uint64_t)tv->tv_usec << 15) / 1000000; 76129e9a116Skettenis offset = time - lemtoh64(data); 76229e9a116Skettenis 76329e9a116Skettenis htolem64(data, offset); 76429e9a116Skettenis return nvmem_write_cell(sc->sc_rtc_node, "rtc_offset", &data, 76529e9a116Skettenis RTC_OFFSET_LEN); 76629e9a116Skettenis } 76729e9a116Skettenis 768c99a06d7Skettenis void 76957f9f74cSkettenis aplsmc_reboot_attachhook(struct device *self) 77057f9f74cSkettenis { 77157f9f74cSkettenis struct aplsmc_softc *sc = (struct aplsmc_softc *)self; 77257f9f74cSkettenis uint8_t count = 0; 77357f9f74cSkettenis 77457f9f74cSkettenis /* Reset error counters. */ 77557f9f74cSkettenis nvmem_write_cell(sc->sc_reboot_node, "boot_error_count", 77657f9f74cSkettenis &count, sizeof(count)); 77757f9f74cSkettenis nvmem_write_cell(sc->sc_reboot_node, "panic_count", 77857f9f74cSkettenis &count, sizeof(count)); 77957f9f74cSkettenis } 78057f9f74cSkettenis 78157f9f74cSkettenis void 782a5505455Skettenis aplsmc_reset(void) 783a5505455Skettenis { 784a5505455Skettenis struct aplsmc_softc *sc = aplsmc_sc; 785a5505455Skettenis uint32_t key = SMC_KEY("MBSE"); 78657f9f74cSkettenis uint32_t rest = SMC_KEY("rest"); 78757f9f74cSkettenis uint32_t phra = SMC_KEY("phra"); 78857f9f74cSkettenis uint8_t boot_stage = 0; 789a5505455Skettenis 79057f9f74cSkettenis aplsmc_write_key(sc, key, &rest, sizeof(rest)); 79157f9f74cSkettenis nvmem_write_cell(sc->sc_reboot_node, "boot_stage", 79257f9f74cSkettenis &boot_stage, sizeof(boot_stage)); 79357f9f74cSkettenis aplsmc_write_key(sc, key, &phra, sizeof(phra)); 79457f9f74cSkettenis } 79557f9f74cSkettenis 79657f9f74cSkettenis void 79757f9f74cSkettenis aplsmc_powerdown(void) 79857f9f74cSkettenis { 79957f9f74cSkettenis struct aplsmc_softc *sc = aplsmc_sc; 80057f9f74cSkettenis uint32_t key = SMC_KEY("MBSE"); 80157f9f74cSkettenis uint32_t offw = SMC_KEY("offw"); 80257f9f74cSkettenis uint32_t off1 = SMC_KEY("off1"); 80357f9f74cSkettenis uint8_t boot_stage = 0; 80457f9f74cSkettenis uint8_t shutdown_flag = 1; 80557f9f74cSkettenis 80657f9f74cSkettenis aplsmc_write_key(sc, key, &offw, sizeof(offw)); 80757f9f74cSkettenis nvmem_write_cell(sc->sc_reboot_node, "boot_stage", 80857f9f74cSkettenis &boot_stage, sizeof(boot_stage)); 80957f9f74cSkettenis nvmem_write_cell(sc->sc_reboot_node, "shutdown_flag", 81057f9f74cSkettenis &shutdown_flag, sizeof(shutdown_flag)); 81157f9f74cSkettenis aplsmc_write_key(sc, key, &off1, sizeof(off1)); 812a5505455Skettenis } 8135acccd09Skettenis 814*eb720dcaSkettenis #ifndef SMALL_KERNEL 815*eb720dcaSkettenis 8165acccd09Skettenis void 8175acccd09Skettenis aplsmc_battery_init(struct aplsmc_softc *sc) 8185acccd09Skettenis { 819*eb720dcaSkettenis uint8_t ch0i, ch0c, chwa; 820*eb720dcaSkettenis int error, stop; 8215acccd09Skettenis 8225acccd09Skettenis error = aplsmc_read_key(sc, SMC_KEY("CH0I"), &ch0i, sizeof(ch0i)); 8235acccd09Skettenis if (error) 8245acccd09Skettenis return; 8255acccd09Skettenis error = aplsmc_read_key(sc, SMC_KEY("CH0C"), &ch0c, sizeof(ch0c)); 8265acccd09Skettenis if (error) 8275acccd09Skettenis return; 8285acccd09Skettenis 8295acccd09Skettenis if (ch0i & CH0I_DISCHARGE) 8305acccd09Skettenis hw_battery_chargemode = -1; 8315acccd09Skettenis else if (ch0c & CH0C_INHIBIT) 8325acccd09Skettenis hw_battery_chargemode = 0; 8335acccd09Skettenis else 8345acccd09Skettenis hw_battery_chargemode = 1; 8355acccd09Skettenis 8365acccd09Skettenis hw_battery_setchargemode = aplsmc_battery_setchargemode; 837*eb720dcaSkettenis 838*eb720dcaSkettenis /* 839*eb720dcaSkettenis * The system firmware for macOS 15 (Sequoia) introduced a new 840*eb720dcaSkettenis * CHLS key that allows setting the level at which to stop 841*eb720dcaSkettenis * charging, and dropped support for the old CHWA key that 842*eb720dcaSkettenis * only supports a fixed limit of 80%. However, CHLS is 843*eb720dcaSkettenis * broken in some beta versions. Those versions still support 844*eb720dcaSkettenis * CHWA so prefer that over CHLS. 845*eb720dcaSkettenis */ 846*eb720dcaSkettenis error = aplsmc_read_key(sc, SMC_KEY("CHWA"), &chwa, sizeof(chwa)); 847*eb720dcaSkettenis if (error) { 848*eb720dcaSkettenis uint16_t chls; 849*eb720dcaSkettenis 850*eb720dcaSkettenis error = aplsmc_read_key(sc, SMC_KEY("CHLS"), 851*eb720dcaSkettenis &chls, sizeof(chls)); 852*eb720dcaSkettenis if (error) 853*eb720dcaSkettenis return; 854*eb720dcaSkettenis stop = (chls & 0xff) ? (chls & 0xff) : 100; 855*eb720dcaSkettenis hw_battery_setchargestop = aplsmc_battery_chls_setchargestop; 856*eb720dcaSkettenis } else { 857*eb720dcaSkettenis stop = chwa ? 80 : 100; 858*eb720dcaSkettenis hw_battery_setchargestop = aplsmc_battery_chwa_setchargestop; 8595acccd09Skettenis } 8605acccd09Skettenis 861*eb720dcaSkettenis hw_battery_setchargestart = aplsmc_battery_setchargestart; 862*eb720dcaSkettenis 863*eb720dcaSkettenis hw_battery_chargestart = stop - 5; 864*eb720dcaSkettenis hw_battery_chargestop = stop; 865*eb720dcaSkettenis } 866*eb720dcaSkettenis 8675acccd09Skettenis int 8685acccd09Skettenis aplsmc_battery_setchargemode(int mode) 8695acccd09Skettenis { 8705acccd09Skettenis struct aplsmc_softc *sc = aplsmc_sc; 8715acccd09Skettenis uint8_t val; 8725acccd09Skettenis int error; 8735acccd09Skettenis 8745acccd09Skettenis switch (mode) { 8755acccd09Skettenis case -1: 8765acccd09Skettenis val = 0; 8775acccd09Skettenis error = aplsmc_write_key(sc, SMC_KEY("CH0C"), 8785acccd09Skettenis &val, sizeof(val)); 8795acccd09Skettenis if (error) 8805acccd09Skettenis return error; 8815acccd09Skettenis val = 1; 8825acccd09Skettenis error = aplsmc_write_key(sc, SMC_KEY("CH0I"), 8835acccd09Skettenis &val, sizeof(val)); 8845acccd09Skettenis if (error) 8855acccd09Skettenis return error; 8865acccd09Skettenis break; 8875acccd09Skettenis case 0: 8885acccd09Skettenis val = 0; 8895acccd09Skettenis error = aplsmc_write_key(sc, SMC_KEY("CH0I"), 8905acccd09Skettenis &val, sizeof(val)); 8915acccd09Skettenis if (error) 8925acccd09Skettenis return error; 8935acccd09Skettenis val = 1; 8945acccd09Skettenis error = aplsmc_write_key(sc, SMC_KEY("CH0C"), 8955acccd09Skettenis &val, sizeof(val)); 8965acccd09Skettenis if (error) 8975acccd09Skettenis return error; 8985acccd09Skettenis break; 8995acccd09Skettenis case 1: 9005acccd09Skettenis val = 0; 9015acccd09Skettenis error = aplsmc_write_key(sc, SMC_KEY("CH0I"), 9025acccd09Skettenis &val, sizeof(val)); 9035acccd09Skettenis if (error) 9045acccd09Skettenis return error; 9055acccd09Skettenis val = 0; 9065acccd09Skettenis error = aplsmc_write_key(sc, SMC_KEY("CH0C"), 9075acccd09Skettenis &val, sizeof(val)); 9085acccd09Skettenis if (error) 9095acccd09Skettenis return error; 9105acccd09Skettenis break; 9115acccd09Skettenis default: 9125acccd09Skettenis return EINVAL; 9135acccd09Skettenis } 9145acccd09Skettenis 9155acccd09Skettenis hw_battery_chargemode = mode; 9165acccd09Skettenis return 0; 9175acccd09Skettenis } 9185acccd09Skettenis 9195acccd09Skettenis int 9205acccd09Skettenis aplsmc_battery_setchargestart(int start) 9215acccd09Skettenis { 9225acccd09Skettenis return EOPNOTSUPP; 9235acccd09Skettenis } 9245acccd09Skettenis 9255acccd09Skettenis int 926*eb720dcaSkettenis aplsmc_battery_chls_setchargestop(int stop) 927*eb720dcaSkettenis { 928*eb720dcaSkettenis struct aplsmc_softc *sc = aplsmc_sc; 929*eb720dcaSkettenis uint16_t chls; 930*eb720dcaSkettenis int error; 931*eb720dcaSkettenis 932*eb720dcaSkettenis if (stop < 10) 933*eb720dcaSkettenis stop = 10; 934*eb720dcaSkettenis 935*eb720dcaSkettenis /* 936*eb720dcaSkettenis * Setting the CHLS_FORCE_DISCHARGE flags makes sure the 937*eb720dcaSkettenis * battery is discharged until the configured charge level is 938*eb720dcaSkettenis * reached when the limit is lowered. 939*eb720dcaSkettenis */ 940*eb720dcaSkettenis chls = (stop == 100 ? 0 : stop) | CHLS_FORCE_DISCHARGE; 941*eb720dcaSkettenis error = aplsmc_write_key(sc, SMC_KEY("CHLS"), &chls, sizeof(chls)); 942*eb720dcaSkettenis if (error) 943*eb720dcaSkettenis return error; 944*eb720dcaSkettenis 945*eb720dcaSkettenis hw_battery_chargestart = stop - 5; 946*eb720dcaSkettenis hw_battery_chargestop = stop; 947*eb720dcaSkettenis 948*eb720dcaSkettenis return 0; 949*eb720dcaSkettenis } 950*eb720dcaSkettenis 951*eb720dcaSkettenis int 952*eb720dcaSkettenis aplsmc_battery_chwa_setchargestop(int stop) 9535acccd09Skettenis { 9545acccd09Skettenis struct aplsmc_softc *sc = aplsmc_sc; 9555acccd09Skettenis uint8_t chwa; 956*eb720dcaSkettenis int error; 9575acccd09Skettenis 9585acccd09Skettenis if (stop <= 80) { 959*eb720dcaSkettenis stop = 80; 9605acccd09Skettenis chwa = 1; 9615acccd09Skettenis } else { 962*eb720dcaSkettenis stop = 100; 9635acccd09Skettenis chwa = 0; 9645acccd09Skettenis } 9655acccd09Skettenis 966*eb720dcaSkettenis error = aplsmc_write_key(sc, SMC_KEY("CHWA"), &chwa, sizeof(chwa)); 967*eb720dcaSkettenis if (error) 968*eb720dcaSkettenis return error; 969*eb720dcaSkettenis 970*eb720dcaSkettenis hw_battery_chargestart = stop - 5; 971*eb720dcaSkettenis hw_battery_chargestop = stop; 972*eb720dcaSkettenis 973*eb720dcaSkettenis return 0; 9745acccd09Skettenis } 975*eb720dcaSkettenis 976d630bf81Sderaadt #endif 977