xref: /openbsd-src/sys/arch/arm64/dev/aplsmc.c (revision eb720dca6d18a593706022e078b8b06158dd1b74)
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