/*
* 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 */
/*
* TODO : Check bounds on PMData in pmgrop
* callers should specify how much room for data is in the buffer
* and that should be respected by the pmgrop
*/
static int pm_receive(u_char *);
static int pm_send(u_char);
/* these functions are called from adb_direct.c */
void pm_setup_adb(void);
void pm_check_adb_devices(int);
int pm_adb_op(u_char *, adbComp *, volatile int *, int);
/* these functions also use the variables of adb_direct.c */
void pm_adb_get_TALK_result(PMData *);
void pm_adb_get_ADB_data(PMData *);
/*
* These variables are in adb_direct.c.
*/
extern u_char *adbBuffer; /* pointer to user data area */
extern adbComp *adbCompRout; /* pointer to the completion routine */
extern volatile int *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 */
adbComp *compRout; /* completion routine pointer */
volatile int *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 *);
#if 0
/*
* Define the external functions
*/
extern int zshard(int); /* from zs.c */
#endif
#ifdef ADB_DEBUG
/*
* This function dumps contents of the PMData
*/
void
pm_printerr(const char *ttl, int rval, int num, const 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)
{
}
/*
* Search for targ in list. list is an area of listlen bytes
* containing null-terminated strings.
*/
static int
strinlist(const char *targ, char *list, int listlen)
{
char *str;
int sl;
int targlen;
/*
* Check the hardware type of the Power Manager
*/
void
pm_init(void)
{
uint32_t regs[10];
PMData pmdata;
char compat[128];
int clen, node, pm_imask;
node = OF_peer(0);
if (node == -1) {
printf("pmu: Failed to get root");
return;
}
clen = OF_getprop(node, "compatible", compat, sizeof(compat));
if (clen <= 0) {
printf("pmu: failed to read root compatible data %d\n", clen);
return;
}
/*
* The PMgrOp routine
*/
int
pmgrop(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;
s = splhigh();
/* disable all interrupts but PM */
via1_vIER = 0x10;
via1_vIER &= read_via_reg(VIA1, vIER);
write_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 */
/* send number of PM data */
num_pm_data = pmdata->num_data;
if (pm_send_cmd_type[pm_cmd] < 0) {
if ((rval = pm_send((u_char)(num_pm_data & 0xff))) != 0)
break; /* timeout */
pmdata->command = 0;
}
/* send PM data */
pm_buf = (u_char *)pmdata->s_buf;
for (i = 0 ; i < num_pm_data; i++)
if ((rval = pm_send(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 */
}
case 0x10: /* ADB data 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 support this event.",
pmdata.data[2], pmdata.num_data,
pmdata.data);
#endif
break;
}
splx(s);
return 1;
}
/*
* Synchronous ADBOp routine for the Power Manager
*/
int
pm_adb_op(u_char *buffer, adbComp *compRout, volatile int *data, int command)
{
int i;
int s;
int rval;
int timo;
PMData pmdata;
struct adbCommand packet;
/* if the command is LISTEN, add number of ADB data to number of PM data */
if ((command & 0xc) == 0x8) {
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 for 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;
}
/* wait until the PM interrupt has occurred */
timo = 0x80000;
while (adbWaiting == 1) {
if (read_via_reg(VIA1, vIFR) & 0x14)
pm_intr(NULL);
#ifdef PM_GRAB_SI
#if 0
zshard(0); /* grab any serial interrupts */
#else
(void)intr_dispatch(0x70);
#endif
#endif
if ((--timo) < 0) {
/* Try to take an interrupt anyway, just in case.
* This has been observed to happen on my ibook
* when i press a key after boot and before adb
* is attached; For example, when booting with -d.
*/
pm_intr(NULL);
if (adbWaiting) {
printf("pm_adb_op: timeout. command = 0x%x\n",command);
splx(s);
return 1;
}
#ifdef ADB_DEBUG
else {
printf("pm_adb_op: missed interrupt. cmd=0x%x\n",command);
}
#endif
}
}
/* this command enables the interrupt by operating ADB devices */
pmdata.command = PMU_ADB_CMD;
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 */
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];
/*
* Thanks to Paul Mackerras and Fabio Riccardi's Linux implementation
* for a clear description of the PMU results.
*/
static int
pm_battery_info_smart(int battery, struct pmu_battery_info *info)
{
PMData p;