root/sys/arch/mac68k/dev/pm_direct.c
/*      $NetBSD: pm_direct.c,v 1.43 2025/05/14 06:31:45 nat Exp $       */

/*
 * Copyright (c) 2024, 2025 Nathanial Sloss <nathanialsloss@yahoo.com.au>
 * All rights reserved.
 *
 * Copyright (C) 1997 Takashi Hamada
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *  This product includes software developed by Takashi Hamada
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/* From: pm_direct.c 1.3 03/18/98 Takashi Hamada */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pm_direct.c,v 1.43 2025/05/14 06:31:45 nat Exp $");

#include "opt_adb.h"

#ifdef DEBUG
#ifndef ADB_DEBUG
#define ADB_DEBUG
#endif
#endif

/* #define      PM_GRAB_SI      1 */

#include <sys/types.h>
#include <sys/kthread.h>
#include <sys/proc.h>
#include <sys/mutex.h>
#include <sys/sysctl.h>
#include <sys/systm.h>

#include <dev/sysmon/sysmonvar.h>

#include <machine/viareg.h>
#include <machine/param.h>
#include <machine/cpu.h>
#include <machine/adbsys.h>

#include <mac68k/mac68k/macrom.h>
#include <mac68k/dev/adbvar.h>
#include <mac68k/dev/pm_direct.h>

/* hardware dependent values */
extern u_short ADBDelay;
extern u_int32_t HwCfgFlags3;
extern struct mac68k_machine_S mac68k_machine;


/* useful macros */
#define PM_SR()                 via_reg(VIA1, vSR)
#define PM_VIA_INTR_ENABLE()    via_reg(VIA1, vIER) = 0x90
#define PM_VIA_INTR_DISABLE()   via_reg(VIA1, vIER) = 0x10
#define PM_VIA_CLR_INTR()       via_reg(VIA1, vIFR) = 0x90
#define PM_SET_STATE_ACKON()    via_reg(VIA2, vBufB) |= 0x04
#define PM_SET_STATE_ACKOFF()   via_reg(VIA2, vBufB) &= ~0x04
#define PM_IS_ON                (0x02 == (via_reg(VIA2, vBufB) & 0x02))
#define PM_IS_OFF               (0x00 == (via_reg(VIA2, vBufB) & 0x02))

/*
 * Variables for internal use
 */
int     pmHardware = PM_HW_UNKNOWN;
u_short pm_existent_ADB_devices = 0x0;  /* each bit expresses the existent ADB device */
u_int   pm_LCD_brightness = 0x0;
u_int   pm_LCD_contrast = 0x0;
u_int   pm_counter = 0;                 /* clock count */
struct sysmon_pswitch pbutton;
struct sysctllog        *sc_log;        /* Sysctl log */

/* these values shows that number of data returned after 'send' cmd is sent */
char pm_send_cmd_type[] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
        0xff, 0x00, 0x02, 0x01, 0x01, 0xff, 0xff, 0xff,
        0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x04, 0x14, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x01, 0x00, 0x02, 0x02, 0xff, 0x01, 0x03, 0x01,
        0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
        0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
        0x01, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x04, 0x04,
        0x04, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x01, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x02, 0x02, 0x02, 0x04, 0xff, 0x00, 0xff, 0xff,
        0x01, 0x01, 0x03, 0x02, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x01, 0x01, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
        0xff, 0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x03, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

/* these values shows that number of data returned after 'receive' cmd is sent */
char pm_receive_cmd_type[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x05, 0x15, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x00, 0x03, 0x03, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x04, 0x04, 0x03, 0x09, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x02, 0xff, 0xff, 0x02, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0x02, 0xff, 0xff, 0xff, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};


/* Spin mutex to seriaize powermanager requests. */
kmutex_t pm_mutex;

/*
 * Define the private functions
 */

/* for debugging */
#ifdef ADB_DEBUG
void    pm_printerr(const char *, int, int, char *);
#endif

int     pm_wait_busy(int);
int     pm_wait_free(int);

/* these functions are for the PB1XX series */
int     pm_receive_pm1(u_char *);
int     pm_send_pm1(u_char, int);
int     pm_pmgrop_pm1(PMData *);
void    pm_intr_pm1(void *);
void    brightness_slider(void *);      /* brightness slider thread */

/* these functions are for the PB Duo series and the PB 5XX series */
int     pm_receive_pm2(u_char *);
int     pm_send_pm2(u_char);
int     pm_pmgrop_pm2(PMData *);
void    pm_intr_pm2(void *);

/* this function is MRG-Based (for testing) */
int     pm_pmgrop_mrg(PMData *);

/* these functions are called from adb_direct.c */
void    pm_setup_adb(void);
void    pm_check_adb_devices(int);
void    pm_intr(void *);
int     pm_adb_op(u_char *, void *, void *, int);
void    pm_hw_setup(void);

/* these functions also use the variables of adb_direct.c */
void    pm_adb_get_TALK_result(PMData *);
void    pm_adb_get_ADB_data(PMData *);
void    pm_adb_poll_next_device_pm1(PMData *);

static void     brightness_sysctl_setup(void *);
static int      sysctl_brightness(SYSCTLFN_PROTO);

/*
 * These variables are in adb_direct.c.
 */
extern u_char   *adbBuffer;     /* pointer to user data area */
extern void     *adbCompRout;   /* pointer to the completion routine */
extern void     *adbCompData;   /* pointer to the completion routine data */
extern int      adbWaiting;     /* waiting for return data from the device */
extern int      adbWaitingCmd;  /* ADB command we are waiting for */
extern int      adbStarting;    /* doing ADB reinit, so do "polling" differently */

#define ADB_MAX_MSG_LENGTH      16
#define ADB_MAX_HDR_LENGTH      8
struct adbCommand {
        u_char  header[ADB_MAX_HDR_LENGTH];     /* not used yet */
        u_char  data[ADB_MAX_MSG_LENGTH];       /* packet data only */
        u_char  *saveBuf;       /* where to save result */
        u_char  *compRout;      /* completion routine pointer */
        u_char  *compData;      /* completion routine data pointer */
        u_int   cmd;            /* the original command for this data */
        u_int   unsol;          /* 1 if packet was unsolicited */
        u_int   ack_only;       /* 1 for no special processing */
};
extern  void    adb_pass_up(struct adbCommand *);

#ifdef ADB_DEBUG
/*
 * This function dumps contents of the PMData
 */
void
pm_printerr(const char *ttl, int rval, int num, char *data)
{
        int i;

        printf("pm: %s:%04x %02x ", ttl, rval, num);
        for (i = 0; i < num; i++)
                printf("%02x ", data[i]);
        printf("\n");
}
#endif



/*
 * Check the hardware type of the Power Manager
 */
void
pm_setup_adb(void)
{
        mutex_init(&pm_mutex, MUTEX_DEFAULT, IPL_HIGH);
        switch (mac68k_machine.machineid) {
                case MACH_MACPB140:
                case MACH_MACPB145:
                case MACH_MACPB160:
                case MACH_MACPB165:
                case MACH_MACPB165C:
                case MACH_MACPB170:
                case MACH_MACPB180:
                case MACH_MACPB180C:
                        pmHardware = PM_HW_PB1XX;

                        memset(&pbutton, 0, sizeof(struct sysmon_pswitch));
                        pbutton.smpsw_name = "PB";
                        pbutton.smpsw_type = PSWITCH_TYPE_POWER;
                        if (sysmon_pswitch_register(&pbutton) != 0)
                                printf("Unable to register soft power\n");
                        brightness_sysctl_setup(NULL);
                        kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL,
                            brightness_slider, NULL, NULL, "britethrd");
                        break;
                case MACH_MACPB150:
                case MACH_MACPB210:
                case MACH_MACPB230:
                case MACH_MACPB250:
                case MACH_MACPB270:
                case MACH_MACPB280:
                case MACH_MACPB280C:
                case MACH_MACPB500:
                case MACH_MACPB190:
                case MACH_MACPB190CS:
                        pmHardware = PM_HW_PB5XX;
#if notyet
                        memset(&pbutton, 0, sizeof(struct sysmon_pswitch));
                        pbutton.smpsw_name = "PB";
                        pbutton.smpsw_type = PSWITCH_TYPE_POWER;
                        if (sysmon_pswitch_register(&pbutton) != 0)
                                printf("Unable to register soft power\n");
#endif
                        break;
                default:
                        break;
        }
}


/*
 * Check the existent ADB devices
 */
void
pm_check_adb_devices(int id)
{
        u_short ed = 0x1;

        ed <<= id;
        pm_existent_ADB_devices |= ed;
}


/*
 * Wait until PM IC is busy
 */
int
pm_wait_busy(int xdelay)
{
        while (PM_IS_ON) {
#ifdef PM_GRAB_SI
                (void)intr_dispatch(0x70);      /* grab any serial interrupts */
#endif
                if ((--xdelay) < 0)
                        return 1;       /* timeout */
        }
        return 0;
}


/*
 * Wait until PM IC is free
 */
int
pm_wait_free(int xdelay)
{
        while (PM_IS_OFF) {
#ifdef PM_GRAB_SI
                (void)intr_dispatch(0x70);      /* grab any serial interrupts */
#endif
                if ((--xdelay) < 0)
                        return 0;       /* timeout */
        }
        return 1;
}



/*
 * Functions for the PB1XX series
 */

/*
 * Receive data from PM for the PB1XX series
 */
int
pm_receive_pm1(u_char *data)
{
        int rval = 0xffffcd34;

        via_reg(VIA2, vDirA) = 0x00;

        switch (1) {
                default:
                        if (pm_wait_busy(0x40) != 0)
                                break;                  /* timeout */

                        PM_SET_STATE_ACKOFF();
                        *data = via_reg(VIA2, 0x200);

                        rval = 0xffffcd33;
                        if (pm_wait_free(0x40) == 0)
                                break;                  /* timeout */

                        rval = 0x00;
                        break;
        }

        PM_SET_STATE_ACKON();
        via_reg(VIA2, vDirA) = 0x00;

        return rval;
}



/*
 * Send data to PM for the PB1XX series
 */
int
pm_send_pm1(u_char data, int timo)
{
        int rval;

        via_reg(VIA2, vDirA) = 0xff;
        via_reg(VIA2, 0x200) = data;

        PM_SET_STATE_ACKOFF();
#if 0
        if (pm_wait_busy(0x400) == 0) {
#else
        if (pm_wait_busy(timo) == 0) {
#endif
                PM_SET_STATE_ACKON();
                if (pm_wait_free(0x40) != 0)
                        rval = 0x0;
                else
                        rval = 0xffffcd35;
        } else {
                rval = 0xffffcd36;
        }

        PM_SET_STATE_ACKON();
        via_reg(VIA2, vDirA) = 0x00;

        return rval;
}


/*
 * My PMgrOp routine for the PB1XX series
 */
int
pm_pmgrop_pm1(PMData *pmdata)
{
        int i;
        int s = 0x81815963;
        u_char via1_vIER, via1_vDirA;
        int rval = 0;
        int num_pm_data = 0;
        u_char pm_cmd;
        u_char pm_data;
        u_char *pm_buf;

        mutex_spin_enter(&pm_mutex);

        /* disable all interrupts but PM */
        via1_vIER = via_reg(VIA1, vIER);
        PM_VIA_INTR_DISABLE();

        via1_vDirA = via_reg(VIA1, vDirA);

        switch (pmdata->command) {
                default:
                        for (i = 0; i < 7; i++) {
                                via_reg(VIA2, vDirA) = 0x00;

                                /* wait until PM is free */
                                if (pm_wait_free(ADBDelay) == 0) {      /* timeout */
                                        via_reg(VIA2, vDirA) = 0x00;
                                        /* restore formar value */
                                        via_reg(VIA1, vDirA) = via1_vDirA;
                                        via_reg(VIA1, vIER) = via1_vIER;
                                        mutex_spin_exit(&pm_mutex);
                                        return 0xffffcd38;
                                }

                                switch (mac68k_machine.machineid) {
                                        case MACH_MACPB160:
                                        case MACH_MACPB165:
                                        case MACH_MACPB165C:
                                        case MACH_MACPB170:
                                        case MACH_MACPB180:
                                        case MACH_MACPB180C:
                                                {
                                                        int xdelay = ADBDelay * 16;

                                                        via_reg(VIA2, vDirA) = 0x00;
                                                        while ((via_reg(VIA2, 0x200) == 0x7f) && (xdelay >= 0))
                                                                xdelay--;

                                                        if (xdelay < 0) {       /* timeout */
                                                                via_reg(VIA2, vDirA) = 0x00;
                                                                /* restore formar value */
                                                                via_reg(VIA1, vIER) = via1_vIER;
                                                                mutex_spin_exit(&pm_mutex);
                                                                return 0xffffcd38;
                                                        }
                                                }
                                } /* end switch */

                                s = splhigh();

                                via1_vDirA = via_reg(VIA1, vDirA);
                                via_reg(VIA1, vDirA) &= 0x7f;

                                pm_cmd = (u_char)(pmdata->command & 0xff);
                                if ((rval = pm_send_pm1(pm_cmd, ADBDelay * 8)) == 0)
                                        break;  /* send command succeeded */

                                via_reg(VIA1, vDirA) = via1_vDirA;
                                splx(s);
                        } /* end for */

                        /* failed to send a command */
                        if (i == 7) {
                                via_reg(VIA2, vDirA) = 0x00;
                                /* restore formar value */
                                via_reg(VIA1, vDirA) = via1_vDirA;
                                via_reg(VIA1, vIER) = via1_vIER;
                                if (s != 0x81815963)
                                        splx(s);
                                mutex_spin_exit(&pm_mutex);
                                return 0xffffcd38;
                        }

                        /* send # of PM data */
                        num_pm_data = pmdata->num_data;
                        if ((rval = pm_send_pm1((u_char)(num_pm_data & 0xff), ADBDelay * 8)) != 0)
                                break;                  /* timeout */

                        /* send PM data */
                        pm_buf = (u_char *)pmdata->s_buf;
                        for (i = 0; i < num_pm_data; i++)
                                if ((rval = pm_send_pm1(pm_buf[i], ADBDelay * 8)) != 0)
                                        break;          /* timeout */
                        if ((i != num_pm_data) && (num_pm_data != 0))
                                break;                  /* timeout */

                        /* Will PM IC return data? */
                        if ((pm_cmd & 0x08) == 0) {
                                rval = 0;
                                break;                  /* no returned data */
                        }

                        rval = 0xffffcd37;
                        if (pm_wait_busy(ADBDelay) != 0)
                                break;                  /* timeout */

                        /* receive PM command */
                        if ((rval = pm_receive_pm1(&pm_data)) != 0)
                                break;

                        pmdata->command = pm_data;

                        /* receive number of PM data */
                        if ((rval = pm_receive_pm1(&pm_data)) != 0)
                                break;                  /* timeout */
                        num_pm_data = pm_data;
                        pmdata->num_data = num_pm_data;

                        /* receive PM data */
                        pm_buf = (u_char *)pmdata->r_buf;
                        for (i = 0; i < num_pm_data; i++) {
                                if ((rval = pm_receive_pm1(&pm_data)) != 0)
                                        break;          /* timeout */
                                pm_buf[i] = pm_data;
                        }

                        rval = 0;
        }

        via_reg(VIA2, vDirA) = 0x00;

        /* restore formar value */
        via_reg(VIA1, vDirA) = via1_vDirA;
        via_reg(VIA1, vIER) = via1_vIER;
        if (s != 0x81815963)
                splx(s);

        mutex_spin_exit(&pm_mutex);

        return rval;
}


/*
 * My PM interrupt routine for PB1XX series
 */
void
pm_intr_pm1(void *arg)
{
        int s;
        int rval;
        PMData pmdata;

        s = splhigh();

        PM_VIA_CLR_INTR();                              /* clear VIA1 interrupt */

        /* ask PM what happened */
        pmdata.command = 0x78;
        pmdata.num_data = 0;
        pmdata.data[0] = pmdata.data[1] = 0;
        pmdata.s_buf = &pmdata.data[2];
        pmdata.r_buf = &pmdata.data[2];
        rval = pm_pmgrop_pm1(&pmdata);
        if (rval != 0) {
#ifdef ADB_DEBUG
                if (adb_debug)
                        printf("pm: PM is not ready. error code=%08x\n", rval);
#endif
                splx(s);
                return;
        }

        if ((pmdata.data[2] & 0x10) == 0x10) {
                if ((pmdata.data[2] & 0x0f) == 0) {
                        /* ADB data that were requested by TALK command */
                        pm_adb_get_TALK_result(&pmdata);
                } else if ((pmdata.data[2] & 0x08) == 0x8) {
                        /* PM is requesting to poll  */
                        pm_adb_poll_next_device_pm1(&pmdata);
                } else if ((pmdata.data[2] & 0x04) == 0x4) {
                        /* ADB device event */
                        pm_adb_get_ADB_data(&pmdata);
                }
        } else if ((pmdata.num_data == 0x1) && (pmdata.data[0] == 0)) {
                /* PowerBook 160/180 Power button. */
                sysmon_pswitch_event(&pbutton, PSWITCH_EVENT_PRESSED);
        } else {
#ifdef ADB_DEBUG
                if (adb_debug)
                        pm_printerr("driver does not supported this event.",
                            rval, pmdata.num_data, pmdata.data);
#endif
        }

        splx(s);
}

void
brightness_slider(void *arg)
{
        int s;
        int rval;
        PMData pmdata;

        for (;;) {
                kpause("brslider", false, hz / 4, NULL);

                s = splhigh();

                pmdata.command = 0x49;
                pmdata.num_data = 0;
                pmdata.data[0] = pmdata.data[1] = 0;
                pmdata.s_buf = &pmdata.data[0];
                pmdata.r_buf = &pmdata.data[0];
                rval = pm_pmgrop_pm1(&pmdata);
                if (rval != 0) {
#ifdef ADB_DEBUG
                        if (adb_debug) {
                                printf("pm: PM is not ready. "
                                    "error code=%08x\n", rval);
                        }
#endif
                        splx(s);
                        continue;
                }

                if (((uint8_t)pmdata.data[0] / 8) != pm_LCD_brightness) {
                        pm_LCD_brightness = (uint8_t)pmdata.data[0] / 8;
                        pm_LCD_brightness =
                            pm_set_brightness(pm_LCD_brightness);
                }

                splx(s);
        }
}

/*
 * Functions for the PB Duo series and the PB 5XX series
 */

/*
 * Receive data from PM for the PB Duo series and the PB 5XX series
 */
int
pm_receive_pm2(u_char *data)
{
        int rval;

        rval = 0xffffcd34;

        switch (1) {
                default:
                        /* set VIA SR to input mode */
                        via_reg(VIA1, vACR) |= 0x0c;
                        via_reg(VIA1, vACR) &= ~0x10;
                        PM_SR();

                        PM_SET_STATE_ACKOFF();
                        if (pm_wait_busy((int)ADBDelay*32) != 0)
                                break;          /* timeout */

                        PM_SET_STATE_ACKON();
                        rval = 0xffffcd33;
                        if (pm_wait_free((int)ADBDelay*32) == 0)
                                break;          /* timeout */

                        *data = PM_SR();
                        rval = 0;

                        break;
        }

        PM_SET_STATE_ACKON();
        via_reg(VIA1, vACR) |= 0x1c;

        return rval;
}



/*
 * Send data to PM for the PB Duo series and the PB 5XX series
 */
int
pm_send_pm2(u_char data)
{
        int rval;

        via_reg(VIA1, vACR) |= 0x1c;
        PM_SR() = data;

        PM_SET_STATE_ACKOFF();
        if (pm_wait_busy((int)ADBDelay*32) == 0) {
                PM_SET_STATE_ACKON();
                if (pm_wait_free((int)ADBDelay*32) != 0)
                        rval = 0;
                else
                        rval = 0xffffcd35;
        } else {
                rval = 0xffffcd36;
        }

        PM_SET_STATE_ACKON();
        via_reg(VIA1, vACR) |= 0x1c;

        return rval;
}



/*
 * My PMgrOp routine for the PB Duo series and the PB 5XX series
 */
int
pm_pmgrop_pm2(PMData *pmdata)
{
        int i;
        int s;
        u_char via1_vIER;
        int rval = 0;
        int num_pm_data = 0;
        u_char pm_cmd;
        short pm_num_rx_data;
        u_char pm_data;
        u_char *pm_buf;

        mutex_spin_enter(&pm_mutex);
        s = splhigh();

        /* disable all interrupts but PM */
        via1_vIER = 0x10;
        via1_vIER &= via_reg(VIA1, vIER);
        via_reg(VIA1, vIER) = via1_vIER;
        if (via1_vIER != 0x0)
                via1_vIER |= 0x80;

        switch (pmdata->command) {
                default:
                        /* wait until PM is free */
                        pm_cmd = (u_char)(pmdata->command & 0xff);
                        rval = 0xcd38;
                        if (pm_wait_free(ADBDelay * 4) == 0)
                                break;                  /* timeout */

                        if (HwCfgFlags3 & 0x00200000) {
                                /* PB 160, PB 165(c), PB 180(c)? */
                                int xdelay = ADBDelay * 16;

                                via_reg(VIA2, vDirA) = 0x00;
                                while ((via_reg(VIA2, 0x200) == 0x07) &&
                                    (xdelay >= 0))
                                        xdelay--;

                                if (xdelay < 0) {
                                        rval = 0xffffcd38;
                                        break;          /* timeout */
                                }
                        }

                        /* send PM command */
                        if ((rval = pm_send_pm2((u_char)(pm_cmd & 0xff))))
                                break;                          /* timeout */

                        /* send number of PM data */
                        num_pm_data = pmdata->num_data;
                        if (HwCfgFlags3 & 0x00020000) {         /* PB Duo, PB 5XX */
                                if (pm_send_cmd_type[pm_cmd] < 0) {
                                        if ((rval = pm_send_pm2((u_char)(num_pm_data & 0xff))) != 0)
                                                break;          /* timeout */
                                        pmdata->command = 0;
                                }
                        } else {                                /* PB 1XX series ? */
                                if ((rval = pm_send_pm2((u_char)(num_pm_data & 0xff))) != 0)
                                        break;                  /* timeout */
                        }
                        /* send PM data */
                        pm_buf = (u_char *)pmdata->s_buf;
                        for (i = 0 ; i < num_pm_data; i++)
                                if ((rval = pm_send_pm2(pm_buf[i])) != 0)
                                        break;                  /* timeout */
                        if (i != num_pm_data)
                                break;                          /* timeout */


                        /* check if PM will send me data  */
                        pm_num_rx_data = pm_receive_cmd_type[pm_cmd];
                        pmdata->num_data = pm_num_rx_data;
                        if (pm_num_rx_data == 0) {
                                rval = 0;
                                break;                          /* no return data */
                        }

                        /* receive PM command */
                        pm_data = pmdata->command;
                        if (HwCfgFlags3 & 0x00020000) {         /* PB Duo, PB 5XX */
                                pm_num_rx_data--;
                                if (pm_num_rx_data == 0)
                                        if ((rval = pm_receive_pm2(&pm_data)) != 0) {
                                                rval = 0xffffcd37;
                                                break;
                                        }
                                pmdata->command = pm_data;
                        } else {                                /* PB 1XX series ? */
                                if ((rval = pm_receive_pm2(&pm_data)) != 0) {
                                        rval = 0xffffcd37;
                                        break;
                                }
                                pmdata->command = pm_data;
                        }

                        /* receive number of PM data */
                        if (HwCfgFlags3 & 0x00020000) {         /* PB Duo, PB 5XX */
                                if (pm_num_rx_data < 0) {
                                        if ((rval = pm_receive_pm2(&pm_data)) != 0)
                                                break;          /* timeout */
                                        num_pm_data = pm_data;
                                } else
                                        num_pm_data = pm_num_rx_data;
                                pmdata->num_data = num_pm_data;
                        } else {                                /* PB 1XX serias ? */
                                if ((rval = pm_receive_pm2(&pm_data)) != 0)
                                        break;                  /* timeout */
                                num_pm_data = pm_data;
                                pmdata->num_data = num_pm_data;
                        }

                        /* receive PM data */
                        pm_buf = (u_char *)pmdata->r_buf;
                        for (i = 0; i < num_pm_data; i++) {
                                if ((rval = pm_receive_pm2(&pm_data)) != 0)
                                        break;                  /* timeout */
                                pm_buf[i] = pm_data;
                        }

                        rval = 0;
        }

        /* restore former value */
        via_reg(VIA1, vIER) = via1_vIER;
        splx(s);

        mutex_spin_exit(&pm_mutex);
        return rval;
}


/*
 * My PM interrupt routine for the PB Duo series and the PB 5XX series
 */
void
pm_intr_pm2(void *arg)
{
        int s;
        int rval;
        PMData pmdata;

        s = splhigh();

        PM_VIA_CLR_INTR();                      /* clear VIA1 interrupt */
                                                /* ask PM what happened */
        pmdata.command = 0x78;
        pmdata.num_data = 0;
        pmdata.s_buf = &pmdata.data[2];
        pmdata.r_buf = &pmdata.data[2];
        rval = pm_pmgrop_pm2(&pmdata);
        if (rval != 0) {
#ifdef ADB_DEBUG
                if (adb_debug)
                        printf("pm: PM is not ready. error code: %08x\n", rval);
#endif
                splx(s);
                return;
        }

        switch ((u_int)(pmdata.data[2] & 0xff)) {
                case 0x00:                      /* 1 sec interrupt? */
                        break;
                case 0x80:                      /* 1 sec interrupt? */
                        pm_counter++;
                        break;
                case 0x08:      /* Brightness/Contrast button on LCD panel */
                        /* get brightness and contrast of the LCD */
                        pm_LCD_brightness = (u_int)pmdata.data[3] & 0xff;
                        pm_LCD_contrast = (u_int)pmdata.data[4] & 0xff;
/*
                        pm_printerr("#08", rval, pmdata.num_data, pmdata.data);
                        pmdata.command = 0x33;
                        pmdata.num_data = 1;
                        pmdata.s_buf = pmdata.data;
                        pmdata.r_buf = pmdata.data;
                        pmdata.data[0] = pm_LCD_contrast;
                        rval = pm_pmgrop_pm2(&pmdata);
                        pm_printerr("#33", rval, pmdata.num_data, pmdata.data);
*/
                        pm_LCD_brightness =
                            pm_set_brightness(pm_LCD_brightness);
                        break;
                case 0x10:                      /* ADB data that were requested by TALK command */
                case 0x14:
                        pm_adb_get_TALK_result(&pmdata);
                        break;
                case 0x16:                      /* ADB device event */
                case 0x18:
                case 0x1e:
                        pm_adb_get_ADB_data(&pmdata);
                        break;
                default:
#ifdef ADB_DEBUG
                        if (adb_debug)
                                pm_printerr("driver does not supported this event.",
                                    pmdata.data[2], pmdata.num_data,
                                    pmdata.data);
#endif
                        break;
        }

        splx(s);
}


/*
 * MRG-based PMgrOp routine
 */
int
pm_pmgrop_mrg(PMData *pmdata)
{
        u_int32_t rval=0;

        __asm volatile(
        "       movl    %1,%%a0 \n"
        "       .word   0xa085  \n"
        "       movl    %%d0,%0"
                : "=g" (rval)
                : "g" (pmdata)
                : "a0","d0");

        return rval;
}


/*
 * My PMgrOp routine
 */
int
pmgrop(PMData *pmdata)
{
        switch (pmHardware) {
                case PM_HW_PB1XX:
                        return (pm_pmgrop_pm1(pmdata));
                        break;
                case PM_HW_PB5XX:
                        return (pm_pmgrop_pm2(pmdata));
                        break;
                default:
                        /* return (pmgrop_mrg(pmdata)); */
                        return 1;
        }
}


/*
 * My PM interrupt routine
 */
void
pm_intr(void *arg)
{
        switch (pmHardware) {
                case PM_HW_PB1XX:
                        pm_intr_pm1(arg);
                        break;
                case PM_HW_PB5XX:
                        pm_intr_pm2(arg);
                        break;
                default:
                        break;
        }
}


void
pm_hw_setup(void)
{
        switch (pmHardware) {
                case PM_HW_PB1XX:
                        via1_register_irq(4, pm_intr_pm1, (void *)0);
                        PM_VIA_CLR_INTR();
                        break;
                case PM_HW_PB5XX:
                        via1_register_irq(4, pm_intr_pm2, (void *)0);
                        PM_VIA_CLR_INTR();
                        break;
                default:
                        break;
        }
}


/*
 * Synchronous ADBOp routine for the Power Manager
 */
int
pm_adb_op(u_char *buffer, void *compRout, void *data, int command)
{
        int i;
        int s;
        int rval;
        int xdelay;
        PMData pmdata;
        struct adbCommand packet;

        if (adbWaiting == 1)
                return 1;

        s = splhigh();
        via_reg(VIA1, vIER) = 0x10;

        adbBuffer = buffer;
        adbCompRout = compRout;
        adbCompData = data;

        pmdata.command = 0x20;
        pmdata.s_buf = pmdata.data;
        pmdata.r_buf = pmdata.data;

        if ((command & 0xc) == 0x8) {           /* if the command is LISTEN, add number of ADB data to number of PM data */
                if (buffer != (u_char *)0)
                        pmdata.num_data = buffer[0] + 3;
        } else {
                pmdata.num_data = 3;
        }

        pmdata.data[0] = (u_char)(command & 0xff);
        pmdata.data[1] = 0;
        if ((command & 0xc) == 0x8) {           /* if the command is LISTEN, copy ADB data to PM buffer */
                if ((buffer != (u_char *)0) && (buffer[0] <= 24)) {
                        pmdata.data[2] = buffer[0];             /* number of data */
                        for (i = 0; i < buffer[0]; i++)
                                pmdata.data[3 + i] = buffer[1 + i];
                } else
                        pmdata.data[2] = 0;
        } else
                pmdata.data[2] = 0;

        if ((command & 0xc) != 0xc) {           /* if the command is not TALK */
                /* set up stuff fNULLor adb_pass_up */
                packet.data[0] = 1 + pmdata.data[2];
                packet.data[1] = command;
                for (i = 0; i < pmdata.data[2]; i++)
                        packet.data[i+2] = pmdata.data[i+3];
                packet.saveBuf = adbBuffer;
                packet.compRout = adbCompRout;
                packet.compData = adbCompData;
                packet.cmd = command;
                packet.unsol = 0;
                packet.ack_only = 1;
                adb_polling = 1;
                adb_pass_up(&packet);
                adb_polling = 0;
        }

        rval = pmgrop(&pmdata);
        if (rval != 0) {
                splx(s);
                return 1;
        }

        adbWaiting = 1;
        adbWaitingCmd = command;

        PM_VIA_INTR_ENABLE();

        /* wait until the PM interrupt has occurred */
        xdelay = 0x80000;
        while (adbWaiting == 1) {
                switch (mac68k_machine.machineid) {
                case MACH_MACPB150:
                case MACH_MACPB210:
                case MACH_MACPB230:     /* daishi tested with Duo230 */
                case MACH_MACPB250:
                case MACH_MACPB270:
                case MACH_MACPB280:
                case MACH_MACPB280C:
                case MACH_MACPB190:
                case MACH_MACPB190CS:
                        pm_intr((void *)0);
                        break;
                default:
                        if ((via_reg(VIA1, vIFR) & 0x10) == 0x10)
                                pm_intr((void *)0);
                        break;
                }
#ifdef PM_GRAB_SI
                (void)intr_dispatch(0x70);      /* grab any serial interrupts */
#endif
                if ((--xdelay) < 0) {
                        splx(s);
                        return 1;
                }
        }

        /* this command enables the interrupt by operating ADB devices */
        if (HwCfgFlags3 & 0x00020000) {         /* PB Duo series, PB 5XX series */
                pmdata.command = 0x20;
                pmdata.num_data = 4;
                pmdata.s_buf = pmdata.data;
                pmdata.r_buf = pmdata.data;
                pmdata.data[0] = 0x00;
                pmdata.data[1] = 0x86;  /* magic spell for awaking the PM */
                pmdata.data[2] = 0x00;
                pmdata.data[3] = 0x0c;  /* each bit may express the existent ADB device */
        } else {                                /* PB 1XX series */
                pmdata.command = 0x20;
                pmdata.num_data = 3;
                pmdata.s_buf = pmdata.data;
                pmdata.r_buf = pmdata.data;
                pmdata.data[0] = (u_char)(command & 0xf0) | 0xc;
                pmdata.data[1] = 0x04;
                pmdata.data[2] = 0x00;
        }
        rval = pmgrop(&pmdata);

        splx(s);
        return rval;
}


void
pm_adb_get_TALK_result(PMData *pmdata)
{
        int i;
        struct adbCommand packet;

        /* set up data for adb_pass_up */
        packet.data[0] = pmdata->num_data-1;
        packet.data[1] = pmdata->data[3];
        for (i = 0; i <packet.data[0]-1; i++)
                packet.data[i+2] = pmdata->data[i+4];

        packet.saveBuf = adbBuffer;
        packet.compRout = adbCompRout;
        packet.compData = adbCompData;
        packet.unsol = 0;
        packet.ack_only = 0;
        adb_polling = 1;
        adb_pass_up(&packet);
        adb_polling = 0;

        adbWaiting = 0;
        adbBuffer = (long)0;
        adbCompRout = (long)0;
        adbCompData = (long)0;
}


void
pm_adb_get_ADB_data(PMData *pmdata)
{
        int i;
        struct adbCommand packet;

        /* set up data for adb_pass_up */
        packet.data[0] = pmdata->num_data-1;    /* number of raw data */
        packet.data[1] = pmdata->data[3];       /* ADB command */
        for (i = 0; i <packet.data[0]-1; i++)
                packet.data[i+2] = pmdata->data[i+4];
        packet.unsol = 1;
        packet.ack_only = 0;
        adb_pass_up(&packet);
}


void
pm_adb_poll_next_device_pm1(PMData *pmdata)
{
        int i;
        int ndid;
        u_short bendid = 0x1;
        PMData tmp_pmdata;

        /* find another existent ADB device to poll */
        for (i = 1; i < 16; i++) {
                ndid = (ADB_CMDADDR(pmdata->data[3]) + i) & 0xf;
                bendid <<= ndid;
                if ((pm_existent_ADB_devices & bendid) != 0)
                        break;
        }

        /* poll the other device */
        tmp_pmdata.command = 0x20;
        tmp_pmdata.num_data = 3;
        tmp_pmdata.s_buf = tmp_pmdata.data;
        tmp_pmdata.r_buf = tmp_pmdata.data;
        tmp_pmdata.data[0] = (u_char)(ndid << 4) | 0xc;
        tmp_pmdata.data[1] = 0x04;      /* magic spell for awaking the PM */
        tmp_pmdata.data[2] = 0x00;
        pmgrop(&tmp_pmdata);
}

void
pm_poweroff(void)
{
        PMData pmdata;
        int attempt = 3;

        while (pmHardware == PM_HW_PB1XX && attempt > 0) {
                pmdata.command = 0xef;
                pmdata.num_data = 0;
                pmdata.data[0] = pmdata.data[1] = 0;
                pmdata.s_buf = &pmdata.data[2];
                pmdata.r_buf = &pmdata.data[2];
                (void)pm_pmgrop_pm1(&pmdata);
                attempt--;
        }

        return;
}

u_int
pm_set_brightness(u_int brightness)
{
        PMData pmdata;

        pmdata.num_data = 1;
        pmdata.s_buf = pmdata.data;
        pmdata.r_buf = pmdata.data;

        switch (pmHardware) {
        case PM_HW_PB5XX:
                /* this is an experimental code */
                pmdata.command = 0x41;
                if ((int)brightness < 0)
                        brightness = 0;
                if ((int)brightness > 31)
                        brightness = 31;
                pmdata.data[0] = (31 - brightness) * 23 / 10 + 37;
                (void)pm_pmgrop_pm2(&pmdata);
                break;
        case PM_HW_PB1XX:
                /* this is an experimental code also */
                pmdata.command = 0x40;
                if ((int)brightness < 0)
                        brightness = 0;
                if ((int)brightness > 31)
                        brightness = 31;
                pmdata.data[0] = 31 - brightness;
                (void)pm_pmgrop_pm1(&pmdata);
                break;
        default:

                return 0;
                break;
        }

        return brightness;
}

static void
brightness_sysctl_setup(void *arg)
{
        const struct sysctlnode *rnode;

        if ((sysctl_createv(&sc_log, 0, NULL, &rnode,
            0, CTLTYPE_NODE, "screen",
            SYSCTL_DESCR("Internal display output device controls"),
            NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0)
                goto fail;

        (void)sysctl_createv(&sc_log, 0, &rnode, NULL,
            CTLFLAG_READWRITE, CTLTYPE_INT, "brightness",
            SYSCTL_DESCR("Current brightness level"),
            sysctl_brightness, 0, NULL, 0, CTL_CREATE, CTL_EOL);

        return;

 fail:
        aprint_error("screen: couldn't add sysctl nodes\n");
}

static int
sysctl_brightness(SYSCTLFN_ARGS)
{
        struct sysctlnode node;
        int val, error;

        node = *rnode;

        val = pm_LCD_brightness;

        node.sysctl_data = &val;
        error = sysctl_lookup(SYSCTLFN_CALL(&node));
        if (error || newp == NULL)
                return error;

        val = pm_set_brightness(val);

        return error;
}