/*      $NetBSD: pm_direct.c,v 1.43 2025/05/14 06:31:45 nat Exp $       */

/*
* Copyright (c) 2024, 2025 Nathanial Sloss <[email protected]>
* 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;
}