NetBSD-5.0.2/sys/arch/hp700/hp700/power.c

Compare this file to the similar file:
Show the results in this format:

/* $NetBSD: power.c,v 1.8 2007/12/30 08:32:14 skrll Exp $ */
/*
 * Copyright (c) 2004 Jochen Kunz.
 * 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. The name of Jochen Kunz may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY JOCHEN KUNZ
 * ``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 JOCHEN KUNZ
 * 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.
 */

/*
 * Support for soft power switch found on most hp700 machines.
 */


#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: power.c,v 1.8 2007/12/30 08:32:14 skrll Exp $");


/*
 * There is no software power switch control available at all when 
 * PDC_SOFT_POWER / PDC_SOFT_POWER_INFO fails.
 *
 * There are two ways of controling power depending on the machine type:
 * - 712 style: We have to use the DR25_PCXL_POWFAIL bit in CPU Diagnose
 *   Register 25 of the PCX-L / PA7100LC CPU to get the status of the power
 *   switch and the LASI power register to control the switch. After a
 *   successful call to PDC_SOFT_POWER / PDC_SOFT_POWER_INFO
 *   pdc_power_info.addr is == 0 on this machines. This was introduced with
 *   the 712, hence 712 style.
 * - B/C-Class style: If PDC_SOFT_POWER / PDC_SOFT_POWER_INFO is successful
 *   pdc_power_info.addr contains the HPA of the power switch status 
 *   register and the power switch is controled via PDC_SOFT_POWER / 
 *   PDC_SOFT_POWER_ENABLE.
 *
 * There is no way of asynchronous notification when the power switch was 
 * switched. Thus we have to poll the status of the switch. (There are rumors
 * about a power interrupt facility on B/C-Class style machines, but I found
 * no documentation about it.)
 *
 * The DR25_PCXL_POWFAIL bit on 712 style machines is a straight line from
 * the hardware, so we have to do dampening in software. This is accomplished
 * by polling it multiple times a second (default 0.2 Hz) and alter the state
 * variable if the state of the DR25_PCXL_POWFAIL bit is constant over a 
 * period of one second.
 * The PWR_SW_REG_POWFAIL bit in the power switch status register is dampened
 * in hardware. So we poll this bit once a second.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/callout.h>
#include <sys/malloc.h>
#include <sys/power.h>
#include <sys/sysctl.h>

#include <dev/sysmon/sysmon_taskq.h>
#include <dev/sysmon/sysmonvar.h>

#include <machine/reg.h>
#include <machine/pdc.h>
#include <machine/bus.h>
#include <machine/cpufunc.h>

#include <hp700/hp700/power.h>

#define PWR_SW_REG_POWFAIL		1
#define LASI_PWR_SW_REG_CTRL_DISABLE	1
#define LASI_PWR_SW_REG_CTRL_ENABLE	0

volatile uint32_t *lasi_pwr_sw_reg;
static bus_space_tag_t pwr_sw_reg_bst;
static bus_space_handle_t pwr_sw_reg_bsh;
int pwr_sw_state;
static int pwr_sw_control;
static const char *pwr_sw_state_str[] = {"off", "on"};
static const char *pwr_sw_control_str[] = {"disabled", "enabled", "locked"};
static int pwr_sw_poll_interval; 
static int pwr_sw_count;
static struct sysmon_pswitch *pwr_sw_sysmon;
static struct callout pwr_sw_callout;


static int pwr_sw_sysctl_state(SYSCTLFN_PROTO);
static int pwr_sw_sysctl_ctrl(SYSCTLFN_PROTO);
static void pwr_sw_sysmon_cb(void *);
static void pwr_sw_poll(void *);


void
pwr_sw_init(bus_space_tag_t bst)
{
	struct pdc_power_info pdc_power_info PDC_ALIGNMENT;
	struct sysctllog *sysctl_log = NULL;
	const struct sysctlnode *pwr_sw_node;
	int error, stage;

	pwr_sw_state = 1;
	/*
	 * Ensure that we have a valid lasi_pwr_sw_reg pointer and that we 
	 * are on a PCX-L / PA7100LC CPU if it is a 712 style machine.
	 */
	if (pdc_call((iodcio_t)pdc, 0, PDC_SOFT_POWER, PDC_SOFT_POWER_INFO,
	    &pdc_power_info, 0) < 0 ||
	    (pdc_power_info.addr == 0 &&
	     (strcmp(hppa_cpu_info->hppa_cpu_info_chip_type, "PCX-L") != 0 ||
	      lasi_pwr_sw_reg == NULL))) {
		printf("No soft power available.\n");
		pwr_sw_reg_bst = NULL;
		lasi_pwr_sw_reg = NULL;
		return;
	}
	if (pdc_power_info.addr != 0) {
		pwr_sw_reg_bst = bst;
		if (bus_space_map(pwr_sw_reg_bst, pdc_power_info.addr, 4, 0,
		    &pwr_sw_reg_bsh) != 0) {
			printf("Can't map power switch status register.\n");
			return;
		}
	}

#ifdef DEBUG
	if (pwr_sw_reg_bst != NULL)
		printf("pwr_sw_init: pdc_power_info.addr=0x%x\n",
		    pdc_power_info.addr);
	else
		printf("pwr_sw_init: lasi_pwr_sw_reg=%p\n", lasi_pwr_sw_reg);
#endif /* DEBUG */

	stage = 0;
	error = sysctl_createv(&sysctl_log, 0, NULL, NULL, 0, 
	    CTLTYPE_NODE, "machdep", NULL, NULL, 0, NULL, 0, 
	    CTL_MACHDEP, CTL_EOL);
	if (error == 0) {
		stage = 1;
		error = sysctl_createv(&sysctl_log, 0, NULL, &pwr_sw_node, 0, 
		    CTLTYPE_NODE, "power_switch", NULL, NULL, 0, NULL, 0,
		    CTL_MACHDEP, CTL_CREATE, CTL_EOL);
	}
	if (error == 0)
		error = sysctl_createv(&sysctl_log, 0, NULL, NULL, 
		    CTLFLAG_READONLY, CTLTYPE_STRING, "state", NULL, 
		    pwr_sw_sysctl_state, 0, NULL, 16, 
		    CTL_MACHDEP, pwr_sw_node->sysctl_num, CTL_CREATE, CTL_EOL);
	if (error == 0)
		error = sysctl_createv(&sysctl_log, 0, NULL, NULL,
		    CTLFLAG_READWRITE, CTLTYPE_STRING, "control", NULL,
		    pwr_sw_sysctl_ctrl, 0, NULL, 16,
		    CTL_MACHDEP, pwr_sw_node->sysctl_num, CTL_CREATE, CTL_EOL);

	if (error == 0) {
		if ((pwr_sw_sysmon = malloc(sizeof(struct sysmon_pswitch),
		    M_DEVBUF, 0)) == NULL)
			error = 1;
	} else
		printf("Can't create sysctl machdep.power_switch\n");

	if (error == 0) {
		stage = 2;
		memset(pwr_sw_sysmon, 0, sizeof(struct sysmon_pswitch));
		pwr_sw_sysmon->smpsw_name = "power switch";
		pwr_sw_sysmon->smpsw_type = PSWITCH_TYPE_POWER;
		error = sysmon_pswitch_register(pwr_sw_sysmon);
	} else 
		printf("Can't malloc sysmon power switch.\n");
	if (error == 0) {
		sysmon_task_queue_init();
		if (pwr_sw_reg_bst == NULL)
			/* Diag Reg. needs software dampening, poll at 0.2 Hz.*/
			pwr_sw_poll_interval = hz / 5;
		else
			/* Power Reg. is hardware dampened, poll at 1 Hz. */
			pwr_sw_poll_interval = hz;
		callout_init(&pwr_sw_callout, 0);
		callout_reset(&pwr_sw_callout, pwr_sw_poll_interval, 
		    pwr_sw_poll, NULL);
		return;
	} else
		printf("Can't register power switch with sysmon.\n");

	switch (stage) {
	case 2:
		free(pwr_sw_sysmon, M_DEVBUF);
		/* FALL THROUGH */
	case 1:
		sysctl_teardown(&sysctl_log);
		/* FALL THROUGH */
	}
}


static void
pwr_sw_sysmon_cb(void *not_used) 
{
	sysmon_pswitch_event(pwr_sw_sysmon, PSWITCH_EVENT_PRESSED);
}



static void
pwr_sw_poll(void *not_used)
{
	uint32_t reg;

	if (pwr_sw_reg_bst == NULL) {
		/* Get Power Fail status from CPU Diagnose Register 25 */
		mfcpu(25, reg);
		reg &= 1 << DR25_PCXL_POWFAIL;
		if (reg == (pwr_sw_state != 0 ? 0 : 1 << DR25_PCXL_POWFAIL))
			pwr_sw_count++;
		else
			pwr_sw_count = 0;
		if (pwr_sw_count == hz / pwr_sw_poll_interval)
			pwr_sw_state = reg == 0 ? 0 : 1;
	} else {
		if ((bus_space_read_4(pwr_sw_reg_bst, pwr_sw_reg_bsh, 0) & 
		    PWR_SW_REG_POWFAIL) == 0)
			pwr_sw_state = 0;
		else
			pwr_sw_state = 1;
	}

	if (pwr_sw_state == 0 && pwr_sw_control != PWR_SW_CTRL_LOCK)
		sysmon_task_queue_sched(0, pwr_sw_sysmon_cb, NULL);
	else
		callout_reset(&pwr_sw_callout, pwr_sw_poll_interval, 
		    pwr_sw_poll, NULL);
}


void
pwr_sw_ctrl(int enable)
{
	struct pdc_power_info pdc_power_info PDC_ALIGNMENT;
	int error;

#ifdef DEBUG
	printf("pwr_sw_control=%d enable=%d\n", pwr_sw_control, enable);
#endif /* DEBUG */
	if (cold != 0) {
		if (panicstr)
			return;
		panic("pwr_sw_ctrl can only be called when machine is warm!");
	}
	if (pwr_sw_reg_bst == NULL && lasi_pwr_sw_reg == NULL)
		return;
	if (enable < PWR_SW_CTRL_MIN || enable > PWR_SW_CTRL_MAX)
		panic("invalid power state in pwr_sw_control: %d", enable);
	pwr_sw_control = enable;
	if (pwr_sw_reg_bst == NULL) {
		if (enable == PWR_SW_CTRL_DISABLE)
			*lasi_pwr_sw_reg = LASI_PWR_SW_REG_CTRL_DISABLE;
		else
			*lasi_pwr_sw_reg = LASI_PWR_SW_REG_CTRL_ENABLE;
	} else {
		error = pdc_call((iodcio_t)pdc, 0, PDC_SOFT_POWER,
		    PDC_SOFT_POWER_ENABLE, &pdc_power_info, 
		    enable == 0 ? 0 : 1);
		if (error != 0)
			printf("pwr_sw_control: failed errorcode=%d\n", error);
	}
}


int
pwr_sw_sysctl_state(SYSCTLFN_ARGS)
{
	struct sysctlnode node;

	node = *rnode;
	node.sysctl_data = __UNCONST(pwr_sw_state_str[pwr_sw_state]);
	return sysctl_lookup(SYSCTLFN_CALL(&node));
}


int
pwr_sw_sysctl_ctrl(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	int i, error;
	char val[16];

	node = *rnode;
	strcpy(val, pwr_sw_control_str[pwr_sw_control]);
	node.sysctl_data = val;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (error != 0 || newp == NULL)
		return error;
	for (i = 0; i <= PWR_SW_CTRL_MAX; i++)
		if (strcmp(val, pwr_sw_control_str[i]) == 0) {
			pwr_sw_ctrl(i);
			return 0;
		}
	return EINVAL;
}