1/*	$OpenBSD: kb3310.c,v 1.24 2024/01/21 07:17:06 miod Exp $	*/
2/*
3 * Copyright (c) 2010 Otto Moerbeek <otto@drijf.net>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/kernel.h>
20#include <sys/systm.h>
21#include <sys/device.h>
22#include <sys/sensors.h>
23#include <sys/timeout.h>
24
25#include <machine/apmvar.h>
26#include <machine/autoconf.h>
27#include <machine/bus.h>
28#include <dev/isa/isavar.h>
29
30#include <dev/pci/glxreg.h>
31
32#include <loongson/dev/bonitoreg.h>
33#include <loongson/dev/kb3310var.h>
34
35#include "apm.h"
36#include "pckbd.h"
37#include "hidkbd.h"
38
39#if NPCKBD > 0 || NHIDKBD > 0
40#include <dev/ic/pckbcvar.h>
41#include <dev/pckbc/pckbdvar.h>
42#include <dev/hid/hidkbdvar.h>
43#endif
44
45struct cfdriver ykbec_cd = {
46	NULL, "ykbec", DV_DULL,
47};
48
49#ifdef KB3310_DEBUG
50#define DPRINTF(x)	printf x
51#else
52#define DPRINTF(x)
53#endif
54
55#define IO_YKBEC		0x381
56#define IO_YKBECSIZE		0x3
57
58static const struct {
59	const char *desc;
60	int type;
61} ykbec_table[] = {
62#define YKBEC_FAN	0
63	{ NULL,				SENSOR_FANRPM },
64#define YKBEC_ITEMP	1
65	{ "Internal temperature",	SENSOR_TEMP },
66#define YKBEC_FCAP	2
67	{ "Battery full charge capacity", SENSOR_AMPHOUR },
68#define YKBEC_BCURRENT	3
69	{ "Battery current",		SENSOR_AMPS },
70#define YKBEC_BVOLT	4
71	{ "Battery voltage",		SENSOR_VOLTS_DC },
72#define YKBEC_BTEMP	5
73	{ "Battery temperature",	SENSOR_TEMP },
74#define YKBEC_CAP	6
75	{ "Battery capacity",		SENSOR_PERCENT },
76#define YKBEC_CHARGING	7
77	{ "Battery charging",		SENSOR_INDICATOR },
78#define YKBEC_AC	8
79	{ "AC-Power",			SENSOR_INDICATOR },
80#define YKBEC_LID	9
81	{ "Lid open",			SENSOR_INDICATOR }
82#define YKBEC_NSENSORS	10
83};
84
85struct ykbec_softc {
86	struct device		sc_dev;
87	bus_space_tag_t		sc_iot;
88	bus_space_handle_t	sc_ioh;
89	struct ksensor		sc_sensor[YKBEC_NSENSORS];
90	struct ksensordev	sc_sensordev;
91#if NPCKBD > 0 || NHIDKBD > 0
92	struct timeout		sc_bell_tmo;
93#endif
94};
95
96static struct ykbec_softc *ykbec_sc;
97static int ykbec_chip_config;
98
99extern void loongson_set_isa_imr(uint);
100
101int	ykbec_match(struct device *, void *, void *);
102void	ykbec_attach(struct device *, struct device *, void *);
103
104const struct cfattach ykbec_ca = {
105	sizeof(struct ykbec_softc), ykbec_match, ykbec_attach
106};
107
108int	ykbec_apminfo(struct apm_power_info *);
109void	ykbec_bell(void *, u_int, u_int, u_int, int);
110void	ykbec_bell_stop(void *);
111void	ykbec_print_bat_info(struct ykbec_softc *);
112u_int	ykbec_read(struct ykbec_softc *, u_int);
113u_int	ykbec_read16(struct ykbec_softc *, u_int);
114void	ykbec_refresh(void *arg);
115void	ykbec_write(struct ykbec_softc *, u_int, u_int);
116
117#if NAPM > 0
118struct apm_power_info ykbec_apmdata;
119const char *ykbec_batstate[] = {
120	"high",
121	"low",
122	"critical",
123	"charging",
124	"unknown"
125};
126#define BATTERY_STRING(x) ((x) < nitems(ykbec_batstate) ? \
127	ykbec_batstate[x] : ykbec_batstate[4])
128#endif
129
130int
131ykbec_match(struct device *parent, void *match, void *aux)
132{
133	struct isa_attach_args *ia = aux;
134	bus_space_handle_t ioh;
135
136	/* XXX maybe allow LOONGSON_EBT700 ??? */
137	if (sys_platform->system_type != LOONGSON_YEELOONG)
138		return (0);
139
140	if ((ia->ia_iobase != IOBASEUNK && ia->ia_iobase != IO_YKBEC) ||
141	    /* (ia->ia_iosize != 0 && ia->ia_iosize != IO_YKBECSIZE) || XXX isa.c */
142	    ia->ia_maddr != MADDRUNK || ia->ia_msize != 0 ||
143	    ia->ia_irq != IRQUNK || ia->ia_drq != DRQUNK)
144		return (0);
145
146	if (bus_space_map(ia->ia_iot, IO_YKBEC, IO_YKBECSIZE, 0, &ioh))
147		return (0);
148
149	bus_space_unmap(ia->ia_iot, ioh, IO_YKBECSIZE);
150
151	ia->ia_iobase = IO_YKBEC;
152	ia->ia_iosize = IO_YKBECSIZE;
153
154	return (1);
155}
156
157void
158ykbec_attach(struct device *parent, struct device *self, void *aux)
159{
160	struct isa_attach_args *ia = aux;
161	struct ykbec_softc *sc = (struct ykbec_softc *)self;
162	int i;
163
164	sc->sc_iot = ia->ia_iot;
165	if (bus_space_map(sc->sc_iot, ia->ia_iobase, ia->ia_iosize, 0,
166	    &sc->sc_ioh)) {
167		printf(": couldn't map I/O space");
168		return;
169	}
170
171	/* Initialize sensor data. */
172	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
173	    sizeof(sc->sc_sensordev.xname));
174	if (sensor_task_register(sc, ykbec_refresh, 5) == NULL) {
175		printf(", unable to register update task\n");
176		return;
177	}
178
179#ifdef KB3310_DEBUG
180	ykbec_print_bat_info(sc);
181#endif
182	printf("\n");
183
184	for (i = 0; i < YKBEC_NSENSORS; i++) {
185		sc->sc_sensor[i].type = ykbec_table[i].type;
186		if (ykbec_table[i].desc)
187			strlcpy(sc->sc_sensor[i].desc, ykbec_table[i].desc,
188			    sizeof(sc->sc_sensor[i].desc));
189		sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
190	}
191
192	sensordev_install(&sc->sc_sensordev);
193
194#if NAPM > 0
195	/* make sure we have the apm state initialized before apm attaches */
196	ykbec_refresh(sc);
197	apm_setinfohook(ykbec_apminfo);
198#endif
199#if NPCKBD > 0 || NHIDKBD > 0
200	timeout_set(&sc->sc_bell_tmo, ykbec_bell_stop, sc);
201#if NPCKBD > 0
202	pckbd_hookup_bell(ykbec_bell, sc);
203#endif
204#if NHIDKBD > 0
205	hidkbd_hookup_bell(ykbec_bell, sc);
206#endif
207#endif
208	ykbec_sc = sc;
209}
210
211void
212ykbec_write(struct ykbec_softc *mcsc, u_int reg, u_int datum)
213{
214	struct ykbec_softc *sc = (struct ykbec_softc *)mcsc;
215	bus_space_tag_t iot = sc->sc_iot;
216	bus_space_handle_t ioh = sc->sc_ioh;
217
218	bus_space_write_1(iot, ioh, 0, (reg >> 8) & 0xff);
219	bus_space_write_1(iot, ioh, 1, (reg >> 0) & 0xff);
220	bus_space_write_1(iot, ioh, 2, datum);
221}
222
223u_int
224ykbec_read(struct ykbec_softc *mcsc, u_int reg)
225{
226	struct ykbec_softc *sc = (struct ykbec_softc *)mcsc;
227	bus_space_tag_t iot = sc->sc_iot;
228	bus_space_handle_t ioh = sc->sc_ioh;
229
230	bus_space_write_1(iot, ioh, 0, (reg >> 8) & 0xff);
231	bus_space_write_1(iot, ioh, 1, (reg >> 0) & 0xff);
232	return bus_space_read_1(iot, ioh, 2);
233}
234
235u_int
236ykbec_read16(struct ykbec_softc *mcsc, u_int reg)
237{
238	u_int val;
239
240	val = ykbec_read(mcsc, reg);
241	return (val << 8) | ykbec_read(mcsc, reg + 1);
242}
243
244#define KB3310_FAN_SPEED_DIVIDER	480000
245
246#define ECTEMP_CURRENT_REG		0xf458
247#define REG_FAN_SPEED_HIGH		0xfe22
248#define REG_FAN_SPEED_LOW		0xfe23
249
250#define REG_DESIGN_CAP_HIGH		0xf77d
251#define REG_DESIGN_CAP_LOW		0xf77e
252#define REG_FULLCHG_CAP_HIGH		0xf780
253#define REG_FULLCHG_CAP_LOW		0xf781
254
255#define REG_DESIGN_VOL_HIGH		0xf782
256#define REG_DESIGN_VOL_LOW		0xf783
257#define REG_CURRENT_HIGH		0xf784
258#define REG_CURRENT_LOW			0xf785
259#define REG_VOLTAGE_HIGH		0xf786
260#define REG_VOLTAGE_LOW			0xf787
261#define REG_TEMPERATURE_HIGH		0xf788
262#define REG_TEMPERATURE_LOW		0xf789
263#define REG_RELATIVE_CAT_HIGH		0xf492
264#define REG_RELATIVE_CAT_LOW		0xf493
265#define REG_BAT_VENDOR			0xf4c4
266#define REG_BAT_CELL_COUNT		0xf4c6
267
268#define REG_BAT_CHARGE			0xf4a2
269#define BAT_CHARGE_AC			0x00
270#define BAT_CHARGE_DISCHARGE		0x01
271#define BAT_CHARGE_CHARGE		0x02
272
273#define REG_POWER_FLAG			0xf440
274#define POWER_FLAG_ADAPTER_IN		(1<<0)
275#define POWER_FLAG_POWER_ON		(1<<1)
276#define POWER_FLAG_ENTER_SUS		(1<<2)
277
278#define REG_BAT_STATUS			0xf4b0
279#define BAT_STATUS_BAT_EXISTS		(1<<0)
280#define BAT_STATUS_BAT_FULL		(1<<1)
281#define BAT_STATUS_BAT_DESTROY		(1<<2)
282#define BAT_STATUS_BAT_LOW		(1<<5)
283
284#define REG_CHARGE_STATUS		0xf4b1
285#define CHARGE_STATUS_PRECHARGE		(1<<1)
286#define CHARGE_STATUS_OVERHEAT		(1<<2)
287
288#define REG_BAT_STATE			0xf482
289#define BAT_STATE_DISCHARGING		(1<<0)
290#define BAT_STATE_CHARGING		(1<<1)
291
292#define	REG_BEEP_CONTROL		0xf4d0
293#define	BEEP_ENABLE			(1<<0)
294
295#define REG_PMUCFG			0xff0c
296#define PMUCFG_STOP_MODE		(1<<7)
297#define PMUCFG_IDLE_MODE		(1<<6)
298#define PMUCFG_LPC_WAKEUP		(1<<5)
299#define PMUCFG_RESET_8051		(1<<4)
300#define PMUCFG_SCI_WAKEUP		(1<<3)
301#define PMUCFG_WDT_WAKEUP		(1<<2)
302#define PMUCFG_GPWU_WAKEUP		(1<<1)
303#define PMUCFG_IRQ_IDLE			(1<<0)
304
305#define REG_USB0			0xf461
306#define REG_USB1			0xf462
307#define REG_USB2			0xf463
308#define USB_FLAG_ON			1
309#define USB_FLAG_OFF			0
310
311#define REG_FAN_CONTROL			0xf4d2
312#define	REG_FAN_ON			1
313#define REG_FAN_OFF			0
314
315#define REG_LID_STATE			0xf4bd
316#define LID_OPEN			1
317#define LID_CLOSED			0
318
319#define YKBEC_SCI_IRQ			0xa
320
321#ifdef KB3310_DEBUG
322void
323ykbec_print_bat_info(struct ykbec_softc *sc)
324{
325	uint bat_status, count, dvolt, dcap;
326
327	printf(": battery ");
328	bat_status = ykbec_read(sc, REG_BAT_STATUS);
329	if (!ISSET(bat_status, BAT_STATUS_BAT_EXISTS)) {
330		printf("absent");
331		return;
332	}
333
334	count = ykbec_read(sc, REG_BAT_CELL_COUNT);
335	dvolt = ykbec_read16(sc, REG_DESIGN_VOL_HIGH);
336	dcap = ykbec_read16(sc, REG_DESIGN_CAP_HIGH);
337	printf("%d cells, design capacity %dmV %dmAh", count, dvolt, dcap);
338}
339#endif
340
341void
342ykbec_refresh(void *arg)
343{
344	struct ykbec_softc *sc = (struct ykbec_softc *)arg;
345	u_int val, bat_charge, bat_status, charge_status, bat_state, power_flag;
346	u_int lid_state, cap_pct, fullcap;
347	int current;
348#if NAPM > 0
349	struct apm_power_info old;
350#endif
351
352	val = ykbec_read16(sc, REG_FAN_SPEED_HIGH) & 0xfffff;
353	if (val != 0) {
354		val = KB3310_FAN_SPEED_DIVIDER / val;
355		sc->sc_sensor[YKBEC_FAN].value = val;
356		CLR(sc->sc_sensor[YKBEC_FAN].flags, SENSOR_FINVALID);
357	} else
358		SET(sc->sc_sensor[YKBEC_FAN].flags, SENSOR_FINVALID);
359
360	val = ykbec_read(sc, ECTEMP_CURRENT_REG);
361	sc->sc_sensor[YKBEC_ITEMP].value = val * 1000000 + 273150000;
362
363	fullcap = ykbec_read16(sc, REG_FULLCHG_CAP_HIGH);
364	sc->sc_sensor[YKBEC_FCAP].value = fullcap * 1000;
365
366	current = ykbec_read16(sc, REG_CURRENT_HIGH);
367	/* sign extend short -> int, int -> int64 will be done next statement */
368	current |= -(current & 0x8000);
369	sc->sc_sensor[YKBEC_BCURRENT].value = -1000 * current;
370
371	sc->sc_sensor[YKBEC_BVOLT].value = ykbec_read16(sc, REG_VOLTAGE_HIGH) *
372	    1000;
373
374	val = ykbec_read16(sc, REG_TEMPERATURE_HIGH);
375	sc->sc_sensor[YKBEC_BTEMP].value = val * 1000000 + 273150000;
376
377	cap_pct = ykbec_read16(sc, REG_RELATIVE_CAT_HIGH);
378	sc->sc_sensor[YKBEC_CAP].value = cap_pct * 1000;
379
380	bat_charge = ykbec_read(sc, REG_BAT_CHARGE);
381	bat_status = ykbec_read(sc, REG_BAT_STATUS);
382	charge_status = ykbec_read(sc, REG_CHARGE_STATUS);
383	bat_state = ykbec_read(sc, REG_BAT_STATE);
384	power_flag = ykbec_read(sc, REG_POWER_FLAG);
385	lid_state = ykbec_read(sc, REG_LID_STATE);
386
387	sc->sc_sensor[YKBEC_CHARGING].value = !!ISSET(bat_state,
388	    BAT_STATE_CHARGING);
389	sc->sc_sensor[YKBEC_AC].value = !!ISSET(power_flag,
390	    POWER_FLAG_ADAPTER_IN);
391
392	sc->sc_sensor[YKBEC_LID].value = !!ISSET(lid_state, LID_OPEN);
393
394	sc->sc_sensor[YKBEC_CAP].status = ISSET(bat_status, BAT_STATUS_BAT_LOW) ?
395		SENSOR_S_CRIT : SENSOR_S_OK;
396
397#if NAPM > 0
398	bcopy(&ykbec_apmdata, &old, sizeof(old));
399	ykbec_apmdata.battery_life = cap_pct;
400	ykbec_apmdata.ac_state = ISSET(power_flag, POWER_FLAG_ADAPTER_IN) ?
401	    APM_AC_ON : APM_AC_OFF;
402	if (!ISSET(bat_status, BAT_STATUS_BAT_EXISTS)) {
403		ykbec_apmdata.battery_state = APM_BATTERY_ABSENT;
404		ykbec_apmdata.minutes_left = 0;
405		ykbec_apmdata.battery_life = 0;
406	} else {
407		if (ISSET(bat_state, BAT_STATE_CHARGING))
408			ykbec_apmdata.battery_state = APM_BATT_CHARGING;
409		else if (ISSET(bat_status, BAT_STATUS_BAT_LOW))
410			ykbec_apmdata.battery_state = APM_BATT_CRITICAL;
411		else if (cap_pct > 50)
412			ykbec_apmdata.battery_state = APM_BATT_HIGH;
413		else
414			ykbec_apmdata.battery_state = APM_BATT_LOW;
415
416		/* if charging, current is positive */
417		if (ISSET(bat_state, BAT_STATE_CHARGING))
418			current = 0;
419		else
420			current = -current;
421		/* XXX Yeeloong draw is about 1A */
422		if (current <= 0)
423			current = 1000;
424		/* XXX at 5?%, the Yeeloong shuts down */
425		if (cap_pct <= 5)
426			cap_pct = 0;
427		else
428			cap_pct -= 5;
429		fullcap = cap_pct * 60 * fullcap / 100;
430		ykbec_apmdata.minutes_left = fullcap / current;
431
432	}
433	if (old.ac_state != ykbec_apmdata.ac_state)
434		apm_record_event(APM_POWER_CHANGE, "AC power",
435			ykbec_apmdata.ac_state ? "restored" : "lost");
436	if (old.battery_state != ykbec_apmdata.battery_state)
437		apm_record_event(APM_POWER_CHANGE, "battery",
438		    BATTERY_STRING(ykbec_apmdata.battery_state));
439#endif
440}
441
442#if NAPM > 0
443int
444ykbec_apminfo(struct apm_power_info *info)
445{
446	bcopy(&ykbec_apmdata, info, sizeof(struct apm_power_info));
447	return 0;
448}
449
450int
451ykbec_suspend()
452{
453	struct ykbec_softc *sc = ykbec_sc;
454	int ctrl;
455
456	/*
457	 * Set up wakeup sources: currently only the internal keyboard.
458	 */
459	loongson_set_isa_imr(1 << 1);
460
461	/* USB */
462	DPRINTF(("USB\n"));
463	ykbec_write(sc, REG_USB0, USB_FLAG_OFF);
464	ykbec_write(sc, REG_USB1, USB_FLAG_OFF);
465	ykbec_write(sc, REG_USB2, USB_FLAG_OFF);
466
467	/* EC */
468	DPRINTF(("REG_PMUCFG\n"));
469	ctrl = PMUCFG_SCI_WAKEUP | PMUCFG_WDT_WAKEUP | PMUCFG_GPWU_WAKEUP |
470	    PMUCFG_LPC_WAKEUP | PMUCFG_STOP_MODE | PMUCFG_RESET_8051;
471	ykbec_write(sc, REG_PMUCFG, ctrl);
472
473	/* FAN */
474	DPRINTF(("FAN\n"));
475	ykbec_write(sc, REG_FAN_CONTROL, REG_FAN_OFF);
476
477	/* CPU */
478	DPRINTF(("CPU\n"));
479	ykbec_chip_config = REGVAL(LOONGSON_CHIP_CONFIG0);
480	enableintr();
481	REGVAL(LOONGSON_CHIP_CONFIG0) = ykbec_chip_config & ~0x7;
482	(void)REGVAL(LOONGSON_CHIP_CONFIG0);
483
484	/*
485	 * When a resume interrupt fires, we will enter the interrupt
486	 * dispatcher, which will do nothing because we are at splhigh,
487	 * and execution flow will return here and continue.
488	 */
489	(void)disableintr();
490
491	return 0;
492}
493
494int
495ykbec_resume()
496{
497	struct ykbec_softc *sc = ykbec_sc;
498
499	/* CPU */
500	DPRINTF(("CPU\n"));
501	REGVAL(LOONGSON_CHIP_CONFIG0) = ykbec_chip_config;
502	(void)REGVAL(LOONGSON_CHIP_CONFIG0);
503
504	/* FAN */
505	DPRINTF(("FAN\n"));
506	ykbec_write(sc, REG_FAN_CONTROL, REG_FAN_ON);
507
508	/* USB */
509	DPRINTF(("USB\n"));
510	ykbec_write(sc, REG_USB0, USB_FLAG_ON);
511	ykbec_write(sc, REG_USB1, USB_FLAG_ON);
512	ykbec_write(sc, REG_USB2, USB_FLAG_ON);
513
514	ykbec_refresh(sc);
515
516	return 0;
517}
518#endif
519
520#if NPCKBD > 0 || NHIDKBD > 0
521void
522ykbec_bell(void *arg, u_int pitch, u_int period, u_int volume, int poll)
523{
524	struct ykbec_softc *sc = (struct ykbec_softc *)arg;
525	int bctrl;
526	int s;
527
528	s = spltty();
529	bctrl = ykbec_read(sc, REG_BEEP_CONTROL);
530	if (timeout_del(&sc->sc_bell_tmo) || volume == 0) {
531		/* inline ykbec_bell_stop(arg); */
532		ykbec_write(sc, REG_BEEP_CONTROL, bctrl & ~BEEP_ENABLE);
533	}
534
535	if (volume != 0) {
536		ykbec_write(sc, REG_BEEP_CONTROL, bctrl | BEEP_ENABLE);
537		if (poll) {
538			delay(period * 1000);
539			ykbec_write(sc, REG_BEEP_CONTROL, bctrl & ~BEEP_ENABLE);
540		} else {
541			timeout_add_msec(&sc->sc_bell_tmo, period);
542		}
543	}
544	splx(s);
545}
546
547void
548ykbec_bell_stop(void *arg)
549{
550	struct ykbec_softc *sc = (struct ykbec_softc *)arg;
551	int s;
552
553	s = spltty();
554	ykbec_write(sc, REG_BEEP_CONTROL,
555	    ykbec_read(sc, REG_BEEP_CONTROL) & ~BEEP_ENABLE);
556	splx(s);
557}
558#endif
559