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
45 struct 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
58 static 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
85 struct 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
96 static struct ykbec_softc *ykbec_sc;
97 static int ykbec_chip_config;
98
99 extern void loongson_set_isa_imr(uint);
100
101 int ykbec_match(struct device *, void *, void *);
102 void ykbec_attach(struct device *, struct device *, void *);
103
104 const struct cfattach ykbec_ca = {
105 sizeof(struct ykbec_softc), ykbec_match, ykbec_attach
106 };
107
108 int ykbec_apminfo(struct apm_power_info *);
109 void ykbec_bell(void *, u_int, u_int, u_int, int);
110 void ykbec_bell_stop(void *);
111 void ykbec_print_bat_info(struct ykbec_softc *);
112 u_int ykbec_read(struct ykbec_softc *, u_int);
113 u_int ykbec_read16(struct ykbec_softc *, u_int);
114 void ykbec_refresh(void *arg);
115 void ykbec_write(struct ykbec_softc *, u_int, u_int);
116
117 #if NAPM > 0
118 struct apm_power_info ykbec_apmdata;
119 const 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
130 int
ykbec_match(struct device * parent,void * match,void * aux)131 ykbec_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
157 void
ykbec_attach(struct device * parent,struct device * self,void * aux)158 ykbec_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
211 void
ykbec_write(struct ykbec_softc * mcsc,u_int reg,u_int datum)212 ykbec_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
223 u_int
ykbec_read(struct ykbec_softc * mcsc,u_int reg)224 ykbec_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
235 u_int
ykbec_read16(struct ykbec_softc * mcsc,u_int reg)236 ykbec_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
322 void
ykbec_print_bat_info(struct ykbec_softc * sc)323 ykbec_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
341 void
ykbec_refresh(void * arg)342 ykbec_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
443 int
ykbec_apminfo(struct apm_power_info * info)444 ykbec_apminfo(struct apm_power_info *info)
445 {
446 bcopy(&ykbec_apmdata, info, sizeof(struct apm_power_info));
447 return 0;
448 }
449
450 int
ykbec_suspend()451 ykbec_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
494 int
ykbec_resume()495 ykbec_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
521 void
ykbec_bell(void * arg,u_int pitch,u_int period,u_int volume,int poll)522 ykbec_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
547 void
ykbec_bell_stop(void * arg)548 ykbec_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