aboutsummaryrefslogtreecommitdiffstats
path: root/roms/openbios/drivers/pmu.c
blob: f74a30bb76b025752687f9009aeac1f57a0a1a84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
/*
 * Device driver for the via-pmu on Apple Powermacs.
 *
 * The VIA (versatile interface adapter) interfaces to the PMU,
 * a 6805 microprocessor core whose primary function is to control
 * battery charging and system power on the PowerBook 3400 and 2400.
 * The PMU also controls the ADB (Apple Desktop Bus) which connects
 * to the keyboard and mouse, as well as the non-volatile RAM
 * and the RTC (real time clock) chip.
 *
 * Copyright (C) 1998 Paul Mackerras and Fabio Riccardi.
 * Copyright (C) 2001-2002 Benjamin Herrenschmidt
 * Copyright (C) 2006-2007 Johannes Berg
 *
 */

#include "config.h"
#include "libopenbios/bindings.h"
#include "drivers/drivers.h"
#include "libc/byteorder.h"
#include "libc/vsprintf.h"

#include "macio.h"
#include "pmu.h"

#undef DEBUG_PMU
#ifdef DEBUG_PMU
#define PMU_DPRINTF(fmt, args...) \
	do { printk("PMU - %s: " fmt, __func__ , ##args); } while (0)
#else
#define PMU_DPRINTF(fmt, args...) do { } while (0)
#endif

#define IO_PMU_OFFSET	0x00016000
#define IO_PMU_SIZE	0x00002000

/* VIA registers - spaced 0x200 bytes apart */
#define RS              0x200           /* skip between registers */
#define B               0               /* B-side data */
#define A               RS              /* A-side data */
#define DIRB            (2*RS)          /* B-side direction (1=output) */
#define DIRA            (3*RS)          /* A-side direction (1=output) */
#define T1CL            (4*RS)          /* Timer 1 ctr/latch (low 8 bits) */
#define T1CH            (5*RS)          /* Timer 1 counter (high 8 bits) */
#define T1LL            (6*RS)          /* Timer 1 latch (low 8 bits) */
#define T1LH            (7*RS)          /* Timer 1 latch (high 8 bits) */
#define T2CL            (8*RS)          /* Timer 2 ctr/latch (low 8 bits) */
#define T2CH            (9*RS)          /* Timer 2 counter (high 8 bits) */
#define SR              (10*RS)         /* Shift register */
#define ACR             (11*RS)         /* Auxiliary control register */
#define PCR             (12*RS)         /* Peripheral control register */
#define IFR             (13*RS)         /* Interrupt flag register */
#define IER             (14*RS)         /* Interrupt enable register */
#define ANH             (15*RS)         /* A-side data, no handshake */

/* Bits in B data register: all active low */
#define TACK		0x08		/* Transfer request (input) */
#define TREQ		0x10		/* Transfer acknowledge (output) */

/* Bits in ACR */
#define SR_CTRL         0x1c            /* Shift register control bits */
#define SR_EXT          0x0c            /* Shift on external clock */
#define SR_OUT          0x10            /* Shift out if 1 */

/* Bits in IFR and IER */
#define IER_SET         0x80            /* set bits in IER */
#define IER_CLR         0               /* clear bits in IER */
#define SR_INT          0x04            /* Shift register full/empty */

/*
 * This table indicates for each PMU opcode:
 * - the number of data bytes to be sent with the command, or -1
 *   if a length byte should be sent,
 * - the number of response bytes which the PMU will return, or
 *   -1 if it will send a length byte.
 */
static const int8_t pmu_data_len[256][2] = {
/*	   0	   1	   2	   3	   4	   5	   6	   7  */
/*00*/  {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*08*/  {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*10*/  { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*18*/  { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0, 0},
/*20*/  {-1, 0},{ 0, 0},{ 2, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},
/*28*/  { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0,-1},
/*30*/  { 4, 0},{20, 0},{-1, 0},{ 3, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*38*/  { 0, 4},{ 0,20},{ 2,-1},{ 2, 1},{ 3,-1},{-1,-1},{-1,-1},{ 4, 0},
/*40*/  { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*48*/  { 0, 1},{ 0, 1},{-1,-1},{ 1, 0},{ 1, 0},{-1,-1},{-1,-1},{-1,-1},
/*50*/  { 1, 0},{ 0, 0},{ 2, 0},{ 2, 0},{-1, 0},{ 1, 0},{ 3, 0},{ 1, 0},
/*58*/  { 0, 1},{ 1, 0},{ 0, 2},{ 0, 2},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},
/*60*/  { 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*68*/  { 0, 3},{ 0, 3},{ 0, 2},{ 0, 8},{ 0,-1},{ 0,-1},{-1,-1},{-1,-1},
/*70*/  { 1, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*78*/  { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{ 5, 1},{ 4, 1},{ 4, 1},
/*80*/  { 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*88*/  { 0, 5},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*90*/  { 1, 0},{ 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*98*/  { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*a0*/  { 2, 0},{ 2, 0},{ 2, 0},{ 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},
/*a8*/  { 1, 1},{ 1, 0},{ 3, 0},{ 2, 0},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*b0*/  {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*b8*/  {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*c0*/  {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*c8*/  {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*d0*/  { 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*d8*/  { 1, 1},{ 1, 1},{-1,-1},{-1,-1},{ 0, 1},{ 0,-1},{-1,-1},{-1,-1},
/*e0*/  {-1, 0},{ 4, 0},{ 0, 1},{-1, 0},{-1, 0},{ 4, 0},{-1, 0},{-1, 0},
/*e8*/  { 3,-1},{-1,-1},{ 0, 1},{-1,-1},{ 0,-1},{-1,-1},{-1,-1},{ 0, 0},
/*f0*/  {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*f8*/  {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
};

/*
 * PMU commands
 */
#define PMU_POWER_CTRL0            0x10  /* control power of some devices */
#define PMU_POWER_CTRL             0x11  /* control power of some devices */
#define PMU_ADB_CMD                0x20  /* send ADB packet */
#define PMU_ADB_POLL_OFF           0x21  /* disable ADB auto-poll */
#define PMU_WRITE_NVRAM            0x33  /* write non-volatile RAM */
#define PMU_READ_NVRAM             0x3b  /* read non-volatile RAM */
#define PMU_SET_RTC                0x30  /* set real-time clock */
#define PMU_READ_RTC               0x38  /* read real-time clock */
#define PMU_SET_VOLBUTTON          0x40  /* set volume up/down position */
#define PMU_BACKLIGHT_BRIGHT       0x41  /* set backlight brightness */
#define PMU_GET_VOLBUTTON          0x48  /* get volume up/down position */
#define PMU_PCEJECT                0x4c  /* eject PC-card from slot */
#define PMU_BATTERY_STATE          0x6b  /* report battery state etc. */
#define PMU_SMART_BATTERY_STATE    0x6f  /* report battery state (new way) */
#define PMU_SET_INTR_MASK          0x70  /* set PMU interrupt mask */
#define PMU_INT_ACK                0x78  /* read interrupt bits */
#define PMU_SHUTDOWN               0x7e  /* turn power off */
#define PMU_CPU_SPEED              0x7d  /* control CPU speed on some models */
#define PMU_SLEEP                  0x7f  /* put CPU to sleep */
#define PMU_POWER_EVENTS           0x8f  /* Send power-event commands to PMU */
#define PMU_I2C_CMD                0x9a  /* I2C operations */
#define PMU_RESET                  0xd0  /* reset CPU */
#define PMU_GET_BRIGHTBUTTON       0xd9  /* report brightness up/down pos */
#define PMU_GET_COVER              0xdc  /* report cover open/closed */
#define PMU_SYSTEM_READY           0xdf  /* tell PMU we are awake */
#define PMU_GET_VERSION            0xea  /* read the PMU version */

/* Bits to use with the PMU_POWER_CTRL0 command */
#define PMU_POW0_ON                0x80  /* OR this to power ON the device */
#define PMU_POW0_OFF               0x00  /* leave bit 7 to 0 to power it OFF */
#define PMU_POW0_HARD_DRIVE        0x04  /* Hard drive power (on wallstreet/lombard ?) */

/* Bits to use with the PMU_POWER_CTRL command */
#define PMU_POW_ON                 0x80  /* OR this to power ON the device */
#define PMU_POW_OFF                0x00  /* leave bit 7 to 0 to power it OFF */
#define PMU_POW_BACKLIGHT          0x01  /* backlight power */
#define PMU_POW_CHARGER            0x02  /* battery charger power */
#define PMU_POW_IRLED              0x04  /* IR led power (on wallstreet) */
#define PMU_POW_MEDIABAY           0x08  /* media bay power (wallstreet/lombard ?) */

/* Bits in PMU interrupt and interrupt mask bytes */
#define PMU_INT_PCEJECT            0x04  /* PC-card eject buttons */
#define PMU_INT_SNDBRT             0x08  /* sound/brightness up/down buttons */
#define PMU_INT_ADB                0x10  /* ADB autopoll or reply data */
#define PMU_INT_BATTERY            0x20  /* Battery state change */
#define PMU_INT_ENVIRONMENT        0x40  /* Environment interrupts */
#define PMU_INT_TICK               0x80  /* 1-second tick interrupt */

/* Other bits in PMU interrupt valid when PMU_INT_ADB is set */
#define PMU_INT_ADB_AUTO           0x04  /* ADB autopoll, when PMU_INT_ADB */
#define PMU_INT_WAITING_CHARGER    0x01  /* ??? */
#define PMU_INT_AUTO_SRQ_POLL      0x02  /* ??? */

/* Bits in the environement message (either obtained via PMU_GET_COVER,
 * or via PMU_INT_ENVIRONMENT on core99 */
#define PMU_ENV_LID_CLOSED         0x01  /* The lid is closed */

/* I2C related definitions */
#define PMU_I2C_MODE_SIMPLE    0
#define PMU_I2C_MODE_STDSUB    1
#define PMU_I2C_MODE_COMBINED  2

#define PMU_I2C_BUS_STATUS     0
#define PMU_I2C_BUS_SYSCLK     1
#define PMU_I2C_BUS_POWER      2

#define PMU_I2C_STATUS_OK          0
#define PMU_I2C_STATUS_DATAREAD    1
#define PMU_I2C_STATUS_BUSY        0xfe

/* PMU PMU_POWER_EVENTS commands */
enum {
    PMU_PWR_GET_POWERUP_EVENTS = 0x00,
    PMU_PWR_SET_POWERUP_EVENTS = 0x01,
    PMU_PWR_CLR_POWERUP_EVENTS = 0x02,
    PMU_PWR_GET_WAKEUP_EVENTS  = 0x03,
    PMU_PWR_SET_WAKEUP_EVENTS  = 0x04,
    PMU_PWR_CLR_WAKEUP_EVENTS  = 0x05,
};

/* Power events wakeup bits */
enum {
    PMU_PWR_WAKEUP_KEY       = 0x01,  /* Wake on key press */
    PMU_PWR_WAKEUP_AC_INSERT = 0x02,  /* Wake on AC adapter plug */
    PMU_PWR_WAKEUP_AC_CHANGE = 0x04,
    PMU_PWR_WAKEUP_LID_OPEN  = 0x08,
    PMU_PWR_WAKEUP_RING      = 0x10,
};

static uint8_t pmu_readb(pmu_t *dev, int reg)
{
    return *(volatile uint8_t *)(dev->base + reg);
    asm volatile("eieio" : : : "memory");
}

static void pmu_writeb(pmu_t *dev, int reg, uint8_t val)
{
    *(volatile uint8_t *)(dev->base + reg) = val;
    asm volatile("eieio" : : : "memory");
}

static void pmu_handshake(pmu_t *dev)
{
    pmu_writeb(dev, B, pmu_readb(dev, B) & ~TREQ);
    while ((pmu_readb(dev, B) & TACK) != 0);

    pmu_writeb(dev, B, pmu_readb(dev, B) | TREQ);
    while ((pmu_readb(dev, B) & TACK) == 0);
}

static void pmu_send_byte(pmu_t *dev, uint8_t val)
{
    pmu_writeb(dev, ACR, pmu_readb(dev, ACR) | SR_OUT | SR_EXT);
    pmu_writeb(dev, SR, val);
    pmu_handshake(dev);
}

static uint8_t pmu_recv_byte(pmu_t *dev)
{
    pmu_writeb(dev, ACR, (pmu_readb(dev, ACR) & ~SR_OUT) | SR_EXT);
    pmu_readb(dev, SR);
    pmu_handshake(dev);

    return pmu_readb(dev, SR);
}

int pmu_request(pmu_t *dev, uint8_t cmd,
                uint8_t in_len, uint8_t *in_data,
                uint8_t *out_len, uint8_t *out_data)
{
    int i, l, out_sz;
    uint8_t d;

    /* Check command data size */
    l = pmu_data_len[cmd][0];
    if (l >= 0 && in_len != l) {
        printk("PMU: Error, request %02x wants %d args, got %d\n",
               cmd, l, in_len);
        return -1;
    }

    /* Make sure PMU is idle */
    while ((pmu_readb(dev, B) & TACK) == 0);

    /* Send command */
    pmu_send_byte(dev, cmd);

    /* Optionally send data length */
    if (l < 0) {
        pmu_send_byte(dev, in_len);
        /* Send data */
    }

    for (i = 0; i < in_len; i++) {
        pmu_send_byte(dev, in_data[i]);
    }

    /* Check response size */
    l = pmu_data_len[cmd][1];
    if (l < 0) {
        l = pmu_recv_byte(dev);
    }

    if (out_len) {
        out_sz = *out_len;
        *out_len = 0;
    } else {
        out_sz = 0;
    }

    if (l > out_sz) {
        printk("PMU: Error, request %02x returns %d bytes"
               ", room for %d\n", cmd, l, out_sz);
    }

    for (i = 0; i < l; i++) {
        d = pmu_recv_byte(dev);
        if (i < out_sz) {
            out_data[i] = d;
            (*out_len)++;
        }
    }

    return 0;
}

#define MAX_REQ_SIZE     128

#ifdef CONFIG_DRIVER_ADB
static int pmu_adb_req(void *host, const uint8_t *snd_buf, int len,
                       uint8_t *rcv_buf)
{
    uint8_t buffer[MAX_REQ_SIZE], *pos, olen;
    int rc;

    PMU_DPRINTF("pmu_adb_req: len=%d: %02x %02x %02x...\n",
                len, snd_buf[0], snd_buf[1], snd_buf[2]);

    if (len >= (MAX_REQ_SIZE - 1)) {
        printk("pmu_adb_req: too big ! (%d)\n", len);
        return -1;
    }

    buffer[0] = snd_buf[0];
    buffer[1] = 0; /* We don't do autopoll */
    buffer[2] = len - 1;

    if (len > 1) {
        memcpy(&buffer[3], &snd_buf[1], len - 1);
    }
    rc = pmu_request(host, PMU_ADB_CMD, len + 2, buffer, NULL, NULL);
    if (rc) {
        printk("PMU adb request failure %d\n", rc);
        return 0;
    }
    olen = MAX_REQ_SIZE;
    rc = pmu_request(host, PMU_INT_ACK, 0, NULL, &olen, buffer);
    if (rc) {
        printk("PMU intack request failure %d\n", rc);
        return 0;
    }
    PMU_DPRINTF("pmu_resp=%d int=0x%02x\n", olen, buffer[0]);
    if (olen <= 2) {
        return 0;
    } else {
        pos = &buffer[3];
        olen -= 3;
        PMU_DPRINTF("ADB resp: 0x%02x 0x%02x\n", buffer[3], buffer[4]);
    }
    memcpy(rcv_buf, pos, olen);

    return olen;
}
#endif

DECLARE_UNNAMED_NODE(ob_pmu, 0, sizeof(int));

static pmu_t *main_pmu;

static void pmu_reset_all(void)
{
    pmu_request(main_pmu, PMU_RESET, 0, NULL, NULL, NULL);
}

static void pmu_poweroff(void)
{
    uint8_t params[] = "MATT";

    pmu_request(main_pmu, PMU_SHUTDOWN, 4, params, NULL, NULL);
}

static void ob_pmu_open(int *idx)
{
    RET(-1);
}

static void ob_pmu_close(int *idx)
{
}

NODE_METHODS(ob_pmu) = {
    { "open",      ob_pmu_open },
    { "close",     ob_pmu_close },
};

DECLARE_UNNAMED_NODE(rtc, 0, sizeof(int));

static void rtc_open(int *idx)
{
    RET(-1);
}

static void rtc_close(int *idx)
{
}

/*
 * get-time ( -- second minute hour day month year )
 *
 */

static const int days_month[12] =
    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static const int days_month_leap[12] =
    { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static inline int is_leap(int year)
{
    return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}

static void rtc_get_time(int *idx)
{
    uint8_t obuf[4], olen;
    ucell second, minute, hour, day, month, year;
    uint32_t now;
    int current;
    const int *days;

    olen = 4;
    pmu_request(main_pmu, PMU_READ_RTC, 0, NULL, &olen, obuf);

    /* seconds since 01/01/1904 */
    now = (obuf[0] << 24) + (obuf[1] << 16) + (obuf[2] << 8) + obuf[3];

    second =  now % 60;
    now /= 60;

    minute = now % 60;
    now /= 60;

    hour = now % 24;
    now /= 24;

    year = now * 100 / 36525;
    now -= year * 36525 / 100;
    year += 1904;

    days = is_leap(year) ?  days_month_leap : days_month;

    current = 0;
    month = 0;
    while (month < 12) {
        if (now <= current + days[month]) {
            break;
        }

        current += days[month];
        month++;
    }
    month++;

    day = now - current;

    PUSH(second);
    PUSH(minute);
    PUSH(hour);
    PUSH(day);
    PUSH(month);
    PUSH(year);
}

/*
 * set-time ( second minute hour day month year -- )
 *
 */

static  void rtc_set_time(int *idx)
{
    uint8_t ibuf[4];
    ucell second, minute, hour, day, month, year;
    const int *days;
    uint32_t now;
    unsigned int nb_days;
    int i;

    year = POP();
    month = POP();
    day = POP();
    hour = POP();
    minute = POP();
    second = POP();

    days = is_leap(year) ?  days_month_leap : days_month;
    nb_days = (year - 1904) * 36525 / 100 + day;
    for (i = 0; i < month - 1; i++) {
        nb_days += days[i];
    }

    now = (((nb_days * 24) + hour) * 60 + minute) * 60 + second;

    ibuf[0] = now >> 24;
    ibuf[1] = now >> 16;
    ibuf[2] = now >> 8;
    ibuf[3] = now;
    pmu_request(main_pmu, PMU_SET_RTC, 4, ibuf, NULL, NULL);
}

NODE_METHODS(rtc) = {
    { "open",      rtc_open },
    { "close",      rtc_close },
    { "get-time",  rtc_get_time },
    { "set-time",  rtc_set_time },
};

static void rtc_init(char *path)
{
    phandle_t aliases;
    char buf[128];

    push_str(path);
    fword("find-device");

    fword("new-device");

    push_str("rtc");
    fword("device-name");

    push_str("rtc");
    fword("device-type");

    push_str("rtc,via-pmu");
    fword("encode-string");
    push_str("compatible");
    fword("property");

    BIND_NODE_METHODS(get_cur_dev(), rtc);
    fword("finish-device");

    aliases = find_dev("/aliases");
    snprintf(buf, sizeof(buf), "%s/rtc", path);
    set_property(aliases, "rtc", buf, strlen(buf) + 1);
}

static void powermgt_init(char *path)
{
    phandle_t ph;

    push_str(path);
    fword("find-device");

    fword("new-device");

    push_str("power-mgt");
    fword("device-name");

    push_str("power-mgt");
    fword("device-type");

    push_str("via-pmu-99");
    fword("encode-string");
    push_str("compatible");
    fword("property");

    push_str("extint-gpio1");
    fword("encode-string");
    push_str("registry-name");
    fword("property");

    /* This is a bunch of magic "Feature" bits for which we only have
     * partial definitions from Darwin. These are taken from a
     * PowerMac3,1 device-tree. They are also identical in a
     * PowerMac5,1 "Cube". Note that more recent machines such as
     * the MacMini (PowerMac10,1) do not have this property, however
     * MacOS 9 seems to require it (it hangs during boot otherwise).
     */
    const char prim[] = { 0x00, 0x00, 0x00, 0xff,
                          0x00, 0x00, 0x00, 0x2c,
                          0x00, 0x03, 0x0d, 0x40,
                          /* Public PM features */
                          /* 0x00000001 : Wake timer supported */
                          /* 0x00000004 : Processor cycling supported */
                          /* 0x00000100 : Can wake on modem ring */
                          /* 0x00000200 : Has monitor dimming support */
                          /* 0x00000400 : Can program startup timer */
                          /* 0x00002000 : Supports wake on LAN */
                          /* 0x00004000 : Can wake on LID/case open */
                          /* 0x00008000 : Can power off PCI on sleep */
                          /* 0x00010000 : Supports deep sleep */
                          0x00, 0x01, 0xe7, 0x05,
                          /* Private PM features */
                          /* 0x00000400 : Supports ICT control */
                          /* 0x00001000 : Supports Idle2 in hardware */
                          /* 0x00002000 : Open case prevents sleep */
                          0x00, 0x00, 0x34, 0x00,
                          0x00, 0x00, 0x00, 0x00,
                          0x00, 0x00, /* # of batteries supported */
                          0x26, 0x0d,
                          0x46, 0x00, 0x02, 0x78,
                          0x78, 0x3c, 0x00 };

    ph = get_cur_dev();
    BIND_NODE_METHODS(ph, rtc);

    set_property(ph, "prim-info", prim, sizeof(prim));

    fword("finish-device");
}

pmu_t *pmu_init(const char *path, phys_addr_t base)
{
    pmu_t *pmu;
    char buf[64];
    phandle_t aliases;

    base += IO_PMU_OFFSET;
    PMU_DPRINTF(" base=" FMT_plx "\n", base);

    pmu = malloc(sizeof(pmu_t));
    if (pmu == NULL) {
        return NULL;
    }

    fword("new-device");

    push_str("via-pmu");
    fword("device-name");

    push_str("via-pmu");
    fword("device-type");

    push_str("pmu");
    fword("encode-string");
    push_str("compatible");
    fword("property");

    PUSH(1);
    fword("encode-int");
    push_str("#address-cells");
    fword("property");

    PUSH(0);
    fword("encode-int");
    push_str("#size-cells");
    fword("property");

    PUSH(IO_PMU_OFFSET);
    fword("encode-int");
    PUSH(IO_PMU_SIZE);
    fword("encode-int");
    fword("encode+");
    push_str("reg");
    fword("property");

    /* On newworld machines the PMU is on interrupt 0x19 */
    PUSH(0x19);
    fword("encode-int");
    PUSH(1);
    fword("encode-int");
    fword("encode+");
    push_str("interrupts");
    fword("property");

    PUSH(0xd0330c);
    fword("encode-int");
    push_str("pmu-version");
    fword("property");

    BIND_NODE_METHODS(get_cur_dev(), ob_pmu);

    aliases = find_dev("/aliases");
    snprintf(buf, sizeof(buf), "%s/via-pmu", path);
    set_property(aliases, "via-pmu", buf, strlen(buf) + 1);
    pmu->base = base;

#ifdef CONFIG_DRIVER_ADB
    if (has_adb()) {
       pmu->adb_bus = adb_bus_new(pmu, &pmu_adb_req);
       adb_bus_init(buf, pmu->adb_bus);
    }
#endif

    rtc_init(buf);
    powermgt_init(buf);

    main_pmu = pmu;

    fword("finish-device");

    bind_func("pmu-power-off", pmu_poweroff);
    feval("['] pmu-power-off to power-off");
    bind_func("pmu-reset-all", pmu_reset_all);
    feval("['] pmu-reset-all to reset-all");

    return pmu;
}