/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * All Rights Reserved, Copyright (c) FUJITSU LIMITED 2006
 */

/*
 * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 */


#include <sys/types.h>
#include <sys/time.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/param.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/file.h>
#include <sys/intr.h>
#include <sys/machsystm.h>

#define	PNLIE_MASK	0x010	/* interrupt enable/disable */
#define	PNLINT_MASK	0x001	/* interrupted flag */

#ifdef DEBUG
int panel_debug = 0;
static void panel_ddi_put8(ddi_acc_handle_t, uint8_t *, uint8_t);
#define	DCMN_ERR(x)	if (panel_debug) cmn_err x

#else

#define	DCMN_ERR(x)
#define	panel_ddi_put8(x, y, z)	ddi_put8(x, y, z)

#endif

static int	panel_getinfo(dev_info_t *, ddi_info_cmd_t, void *,  void **);
static int	panel_attach(dev_info_t *, ddi_attach_cmd_t);
static int	panel_detach(dev_info_t *, ddi_detach_cmd_t);
static uint_t	panel_intr(caddr_t);
static int	panel_open(dev_t *, int, int, cred_t *);
static int	panel_close(dev_t, int, int, cred_t *);

static char	*panel_name = "oplpanel";
int		panel_enable = 1;	/* enable or disable */

extern uint64_t	cpc_level15_inum;	/* in cpc_subr.c */

struct panel_state {
	dev_info_t		*dip;
	ddi_iblock_cookie_t	iblock_cookie;
	ddi_acc_handle_t	panel_regs_handle;
	uint8_t			*panelregs;		/* mapping address */
	uint8_t			panelregs_state;	/* keeping regs. */
};

struct cb_ops panel_cb_ops = {
	nodev,		/* open */
	nodev,		/* close */
	nodev,		/* strategy */
	nodev,		/* print */
	nodev,		/* dump */
	nodev,		/* read */
	nodev,		/* write */
	nodev,		/* ioctl */
	nodev,		/* devmap */
	nodev,		/* mmap */
	nodev,		/* segmap */
	nochpoll,	/* poll */
	nodev,		/* prop_op */
	NULL,		/* streamtab */
	D_NEW | D_MP | D_HOTPLUG,	/* flag */
	CB_REV,		/* cb_rev */
	nodev,		/* async I/O read entry point */
	nodev		/* async I/O write entry point */
};

static struct dev_ops panel_dev_ops = {
	DEVO_REV,		/* driver build version */
	0,			/* device reference count */
	panel_getinfo,		/* getinfo */
	nulldev,		/* identify */
	nulldev,		/* probe */
	panel_attach,		/* attach */
	panel_detach,		/* detach */
	nulldev,		/* reset */
	&panel_cb_ops,		/* cb_ops */
	NULL,			/* bus_ops */
	nulldev,		/* power */
	ddi_quiesce_not_supported,	/* devo_quiesce */
};

/* module configuration stuff */
static void		*panelstates;
extern struct mod_ops	mod_driverops;

static struct modldrv modldrv = {
	&mod_driverops,
	"OPL panel driver",
	&panel_dev_ops
};

static struct modlinkage modlinkage = {
	MODREV_1,
	&modldrv,
	0
};


int
_init(void)
{
	int	status;

	DCMN_ERR((CE_CONT, "%s: _init\n", panel_name));

	status = ddi_soft_state_init(&panelstates,
	    sizeof (struct panel_state), 0);
	if (status != 0) {
		cmn_err(CE_WARN, "%s: ddi_soft_state_init failed.",
		    panel_name);
		return (status);
	}

	status = mod_install(&modlinkage);
	if (status != 0) {
		ddi_soft_state_fini(&panelstates);
	}

	return (status);
}

int
_fini(void)
{
	/*
	 * Can't unload to make sure the panel switch always works.
	 */
	return (EBUSY);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

static int
panel_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{

	int instance;
	struct panel_state *statep = NULL;

	ddi_device_acc_attr_t access_attr = {
		DDI_DEVICE_ATTR_V0,
		DDI_STRUCTURE_BE_ACC,
		DDI_STRICTORDER_ACC
	};

	instance = ddi_get_instance(dip);

	DCMN_ERR((CE_CONT, "%s%d: attach\n", panel_name, instance));

	switch (cmd) {
	case DDI_ATTACH:
		DCMN_ERR((CE_CONT, "%s%d: DDI_ATTACH\n",
		    panel_name, instance));
		break;

	case DDI_RESUME:
		DCMN_ERR((CE_CONT, "%s%d: DDI_RESUME\n",
		    panel_name, instance));

		if ((statep = (struct panel_state *)
		    ddi_get_soft_state(panelstates, instance)) == NULL) {
			cmn_err(CE_WARN, "%s%d: ddi_get_soft_state failed.",
			    panel_name, instance);
			return (DDI_FAILURE);
		}

		/* enable the interrupt just in case */
		panel_ddi_put8(statep->panel_regs_handle, statep->panelregs,
		    statep->panelregs_state);
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	/*
	 * Attach routine
	 */

	/* alloc and get soft state */
	if (ddi_soft_state_zalloc(panelstates, instance) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "%s%d: ddi_soft_state_zalloc failed.",
		    panel_name, instance);
		goto attach_failed2;
	}
	if ((statep = (struct panel_state *)
	    ddi_get_soft_state(panelstates, instance)) == NULL) {
		cmn_err(CE_WARN, "%s%d: ddi_get_soft_state failed.",
		    panel_name, instance);
		goto attach_failed1;
	}

	/* set the dip in the soft state */
	statep->dip = dip;

	/* mapping register */
	if (ddi_regs_map_setup(dip, 0, (caddr_t *)&statep->panelregs,
	    0, 0, /* the entire space is mapped */
	    &access_attr, &statep->panel_regs_handle) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "%s%d: ddi_regs_map_setup failed.",
		    panel_name, instance);
		goto attach_failed1;
	}

	/* setup the interrupt handler */
	(void) ddi_get_iblock_cookie(dip, 0, &statep->iblock_cookie);
	if (ddi_add_intr(dip, 0, &statep->iblock_cookie, 0, &panel_intr,
	    (caddr_t)statep) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "%s%d: cannot add interrupt handler.",
		    panel_name, instance);
		goto attach_failed0;
	}

	/* ATTACH SUCCESS */

	/* announce the device */
	ddi_report_dev(dip);

	/* turn on interrupt */
	statep->panelregs_state = 0 | PNLIE_MASK;
	panel_ddi_put8(statep->panel_regs_handle, statep->panelregs,
	    statep->panelregs_state);

	return (DDI_SUCCESS);

attach_failed0:
	ddi_regs_map_free(&statep->panel_regs_handle);
attach_failed1:
	ddi_soft_state_free(panelstates, instance);
attach_failed2:
	DCMN_ERR((CE_NOTE, "%s%d: attach failed", panel_name, instance));
	return (DDI_FAILURE);
}

static int
panel_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	int instance;
	struct panel_state *statep;

	instance = ddi_get_instance(dip);

	DCMN_ERR((CE_CONT, "%s%d: detach\n", panel_name, instance));

	if ((statep = (struct panel_state *)
	    ddi_get_soft_state(panelstates, instance)) == NULL) {
		cmn_err(CE_WARN, "%s%d: ddi_get_soft_state failed.",
		    panel_name, instance);
		return (DDI_FAILURE);
	}

	switch (cmd) {
	case DDI_DETACH:
		DCMN_ERR((CE_CONT, "%s%d: DDI_DETACH\n",
		    panel_name, instance));

		/* turn off interrupt */
		statep->panelregs_state &= ~PNLIE_MASK;
		panel_ddi_put8(statep->panel_regs_handle, statep->panelregs,
		    statep->panelregs_state);

		/* free all resources for the dip */
		ddi_remove_intr(dip, 0, statep->iblock_cookie);

		/* need not free iblock_cookie */
		ddi_regs_map_free(&statep->panel_regs_handle);
		ddi_soft_state_free(panelstates, instance);

		return (DDI_SUCCESS);

	case DDI_SUSPEND:
		DCMN_ERR((CE_CONT, "%s%d: DDI_SUSPEND\n",
		    panel_name, instance));
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);

	}
	/* Not reached */
}

/*ARGSUSED*/
static int
panel_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,  void **resultp)
{
	struct panel_state *statep;
	int	instance;
	dev_t	dev = (dev_t)arg;

	instance = getminor(dev);

	DCMN_ERR((CE_CONT, "%s%d: getinfo\n", panel_name, instance));

	switch (cmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if ((statep = (struct panel_state *)
		    ddi_get_soft_state(panelstates, instance)) == NULL) {
			cmn_err(CE_WARN, "%s%d: ddi_get_soft_state failed.",
			    panel_name, instance);
			*resultp = NULL;
			return (DDI_FAILURE);
		}
		*resultp = statep->dip;
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*resultp = (void *)(uintptr_t)instance;
		break;
	default:
		return (DDI_FAILURE);
	}

	return (DDI_SUCCESS);
}

static  uint_t
panel_intr(caddr_t arg)
{
	struct panel_state *statep = (struct panel_state *)arg;

	/* to confirm the validity of the interrupt */
	if (!(ddi_get8(statep->panel_regs_handle, statep->panelregs) &
	    PNLINT_MASK)) {
		return (DDI_INTR_UNCLAIMED);
	}

	/*
	 * Clear the PNLINT bit
	 * HW reported that there might be a delay in the PNLINT bit
	 * clearing. We force synchronization by attempting to read
	 * back the reg after clearing the bit.
	 */
	panel_ddi_put8(statep->panel_regs_handle, statep->panelregs,
	    statep->panelregs_state | PNLINT_MASK);
	(void) ddi_get8(statep->panel_regs_handle, statep->panelregs);

	if (panel_enable) {
		/* avoid double panic */
		panel_enable 	= 0;

		cmn_err(CE_PANIC,
		    "System Panel Driver: Emergency panic request "
		    "detected!");
		/* Not reached */
	}

	return (DDI_INTR_CLAIMED);
}

#ifdef DEBUG
static void
panel_ddi_put8(ddi_acc_handle_t handle, uint8_t *dev_addr, uint8_t value)
{
	if (panel_debug) {
		cmn_err(CE_CONT, "%s: old value = 0x%x\n",
		    panel_name, ddi_get8(handle, dev_addr));
		cmn_err(CE_CONT, "%s: writing value = 0x%x\n",
		    panel_name, value);
		ddi_put8(handle, dev_addr, value);
		cmn_err(CE_CONT, "%s: new value = 0x%x\n",
		    panel_name, ddi_get8(handle, dev_addr));
	} else {
		ddi_put8(handle, dev_addr, value);
	}
}
#endif