/*
 * Written by: Garry Forsgren, Unisys Corporation
 *             Natalie Protasevich, Unisys Corporation
 * This file contains the code to configure and interface 
 * with Unisys ES7000 series hardware system manager (MIP)
 *
 * Copyright (c) 2002 Unisys Corporation.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston MA 02111-1307, USA.
 *
 * Contact information: Unisys Corporation, Township Line & Union Meeting 
 * Roads-A, Unisys Way, Blue Bell, Pennsylvania, 19424, or:
 *
 * http://www.unisys.com
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/smp.h>
#include <linux/string.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <asm/io.h>
#include <asm/smp.h>
#include <asm/apicdef.h>
#include "es7000.h"
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <asm/smpboot.h>

/*
 * ES7000 Globals
 */

int			es7000_plat = 0;
unsigned char 		*es7000_trampoline_base;
int                     es7000_mipup;
struct mbox  		*mip_mbox;  
struct mbox  		*host_mbox;
mbox_t          	es7000_mbox;
unsigned long long 	mip_sequence = 0;
spinlock_t 		es7000_slock;
int             	es7000_ipi_vec = UIS_IPI;
char    		es7000_mpal_rev[8] = CURRENT_REV;

struct es7000_oem_table es7000_oem_table_p;
mip_rqst_t 		host_msg[HOST_CMD_MAX + 1]; 
mip_rqst_t 		mip_msg[MIP_CMD_MAX + 1];  

void (*es7000_ipi_handler)(int, void *, struct pt_regs *);

int     mip_iv;
int     mip_port;
int     mip_type;
int     mip_ver;
int     mip_did;
int     mip_edid;
int     mip_dmode;
int     mip_min; 
int     mip_max;

volatile unsigned long *psai = NULL;
extern int apic_dfr_value;
extern unsigned long logical_cpu_present_map;

/*
 * Parse the OEM Table
 */

void __init
parse_unisys_oem (struct mp_config_oemtable *oemptr)
{
        int                     i, oem_entries;
        char                    *tp = NULL;  
        struct psai             *psaip = NULL;
        unsigned char           type, size;
        struct mip_mailbox_info *mi;
	unsigned long    	temp;
	struct mbox		*host, *mip;
	int 			success = 0;

        temp = (unsigned long)oemptr;
        oem_entries = oemptr->oem_length;

        tp = (char *)temp;

        Dprintk("OEM table at %lx, %x entries\n", 
		(unsigned long)tp, oem_entries);

        tp += 8;

        for(i=0; i <= oem_entries; i++) {
                type = *tp++;
                size = *tp++;
                tp -= 2;
                switch (type) {
                case MIP_MAILBOX:
                        Dprintk("MIP Mailbox entry\n");
                        mi = (struct mip_mailbox_info *)tp;
                        host = (struct mbox *)MIP_RD_LO(mi->host_mip_mail_addr);
                        host_mbox = __va(host);
                        mip = (struct mbox *)MIP_RD_LO(mi->mip_host_mail_addr);
                        mip_mbox = __va(mip);
                        mip_iv = MIP_IV(mi->mip_info);
                        mip_port = MIP_PORT(mi->mip_info);
                        mip_ver = MIP_VER(mi->mip_info);
                        mip_did = MIP_DID(mi->delivery_info);
                        mip_edid = MIP_EDID(mi->delivery_info);
                        mip_dmode = MIP_DMODE(mi->delivery_info);
                        mip_min = MIP_MIN(mi->delivery_info);
                        mip_max = MIP_MAX(mi->delivery_info);
                        Dprintk("es7000_mipcfg: host_mbox = 0x%lx \n", 
				(unsigned long)host_mbox);
                        Dprintk("es7000_mipcfg: mip_mbox = 0x%lx \n", 
				(unsigned long)mip_mbox);
                        Dprintk("es7000_mipcfg: mip_iv = 0x%x \n", mip_iv);
                        Dprintk("es7000_mipcfg: mip_port = 0x%x \n", mip_port);
			success++;
                        break;
                case PSAI:
			psaip = (struct psai *)tp;
			if (tp != NULL) {
				if (psaip->addr)
					psai = __va(psaip->addr);
				else
					psai = NULL;
				success++;
			}
                        break;
                default:
                        break;
                }
                if (i == 6) break;
                tp += size;
        }

        es7000_slock = SPIN_LOCK_UNLOCKED;
	if (success > 1) {
		printk("Enabling ES7000 specific features...");
		es7000_plat = 1;
		clustered_apic_mode = CLUSTERED_APIC_XAPIC;
		apic_broadcast_id = APIC_BROADCAST_ID_XAPIC;
		int_dest_addr_mode = APIC_DEST_PHYSICAL;
		int_delivery_mode = dest_Fixed;
		esr_disable = 1;
		apic_dfr_value = APIC_DFR_FLAT;
		if (apic_version[boot_cpu_physical_apicid] == 0x11) {
			es7000_plat = 2;
			printk(" for Pentium III system");
			clustered_apic_mode = CLUSTERED_APIC_NUMAQ;
			int_dest_addr_mode = APIC_DEST_LOGICAL;
			int_delivery_mode = dest_LowestPrio;
			apic_dfr_value = APIC_DFR_CLUSTER;
		}
		printk("\n");
		es7000_register_crash();
		return;
	}
	es7000_plat = 0;
        return;
}

int
es7000_set_status( unsigned long long status, int which, int offset )
{
        mbox_t  *t;
        char    *line;
        mbox_t  *host, *mip;
        host = host_mbox;
        mip = mip_mbox;

        if (!which) {
                line = "mip";
                t = mip;
        } else {
                line = "host";
                t = host;
        }

        switch (offset) {

        case OFF_0:
                t->off_0 = status;
                break;
        case OFF_8:
                t->off_8 = status;
                break;
        case OFF_10:
                t->off_10 = status;
                break;
        case OFF_18:
                t->off_18 = status;
                break;
        case OFF_38:
                t->off_38 = status;
                break;
        default:
                printk("Cannot set status %s to %llx\n", line, status);
        }

        return(0);
}

EXPORT_SYMBOL(es7000_set_status);

int
es7000_read_mailbox( mbox_t *mbox, int which )
{
        mbox_t  *t;
        mbox_t  *host, *mip;

        host = host_mbox;
        mip = mip_mbox;

        if (!which) {
                t = mip;
        } else {
                t = host;
        }

        bcopy(t, mbox, sizeof(mbox_t));

        return(0);
}

EXPORT_SYMBOL_NOVERS(es7000_read_mailbox);

int
es7000_write_mailbox( mbox_t *mbox, int which )
{
        mbox_t  *t;

        if (!which) {
                t = mip_mbox;
        } else {
                t = host_mbox;
        }

        bcopy(mbox, t, sizeof(mbox_t));

        return(0);
}

EXPORT_SYMBOL_NOVERS(es7000_write_mailbox);

void
es7000_spin(int n)
{
        int i = 0;

        while (i<n) i++;
}

int
es7000_mailit(mip_rqst_t *req)
{
        mbox_t          *host, *mip;
        int             spin;
        unsigned long   flags;
        unsigned long   long seq;

        host = host_mbox;
        mip = mip_mbox;

        spin_lock_irqsave(&es7000_slock,flags);

        if (!DIRECT(req->host_mip.off_0))  {
                seq = ++mip_sequence;
                seq = (seq << 32);
                req->host_mip.off_0 |= seq;
        }

        if((req->host_mip.off_38 & VALID) == 0) {
                req->host_mip.off_38 |= VALID;
	}

        spin = 1000000;
        while (((unsigned long long)host->off_38 & VALID) != 0) {
                if (--spin <= 0) {
                        if (!DIRECT(req->host_mip.off_0))  {
                                --mip_sequence;
                        }
                        spin_unlock_irqrestore(&es7000_slock,flags);
                        return EAGAIN;
                }
                es7000_spin(100);
        }

        bcopy((void *)&req->host_mip, (void *)host, sizeof(mbox_t));

        outb(1, mip_port);

        if(DIRECT(req->host_mip.off_0)) {
                req->state &= ~BUSY;
        }

        spin_unlock_irqrestore(&es7000_slock,flags);
        return(0);
}

EXPORT_SYMBOL_NOVERS(es7000_mailit);

/*
 * int
 * es7000_mipsend
 *
 * Calling/Exit State:
 *      None.
 *
 */

int
es7000_mipsend(struct mbox *mbox)
{
        unsigned long long      seq;
        int                     status = 0;
        int                     spin;
        int i;

        if (!DIRECT(mbox->off_0))  {
                seq = ++mip_sequence;
                seq = (seq << 32);
                mbox->off_0 |= seq;
        }

        if (!(mbox->off_38 & VALID)) {
                mbox->off_38 |= VALID;
        }

        spin = 1000000;
        while (((unsigned long long)host_mbox->off_38 &
                (unsigned long long)VALID) != 0) {
                if (--spin <= 0) {
                        printk("es7000_mipsend: Timeout waiting for Host Valid Flag");
                        return -1;
                }
                es7000_spin(1000000);
        }

        bcopy(mbox, host_mbox, sizeof(struct mbox));
        outb(1, mip_port);

        spin = 1000000;
        i = 0;
        while (((unsigned long long)mip_mbox->off_38 &
                (unsigned long long)VALID) == 0) {
                if (--spin <= 0) {
                        printk("es7000_mipsend: Timeout waiting for MIP Valid Flag");
                        return -1;
                }
                es7000_spin(1000000);
                i++;
                if (i == 100000) {
                        printk("Nothing happened..\n");
                        break;
                }
        }

        status = ((unsigned long long)mip_mbox->off_0 &
                  (unsigned long long)0xffff0000000000) >> 48;
        mip_mbox->off_38 = ((unsigned long long)mip_mbox->off_38 &
                  (unsigned long long)~VALID);
        return status;

}


void
es7000_cur_version(char *ver)
{
        bcopy(es7000_mpal_rev, ver, sizeof(es7000_mpal_rev));
}

EXPORT_SYMBOL_NOVERS(es7000_cur_version);

/*
 * void
 * es7000_reboot()
 *
 * Calling/Exit State:
 *      None.
 *
 */
#define ES7000_SHUTDOWN         0
#define ES7000_REBOOT           1
#define ES7000_CRASH            2
#define ES7000_POWERDOWN        3
int
es7000_reboot(struct notifier_block *self, unsigned long code, void *unused)
{
        mip_rqst_t *req;
        int rv;
	req = (mip_rqst_t *)&host_msg[FUNC(SHUTDOWN)];
	bzero(req, sizeof(mip_rqst_t));
	req->state |= BUSY;
	req->host_mip.off_0 = SHUTDOWN;
	req->host_mip.off_8 = (long long)((code == 1)?1:0);
	req->host_mip.off_38 = VALID;
	printk("\nTaking down ES7000, will %s\n", (code == 1) ? "reboot" : "halt");
	if ((rv = es7000_mipsend((mbox_t *)req)) != 0)
		printk("es7000_crash: SHUTDOWN failed: rv: %d\n", rv);
	req->state &= ~BUSY;
	return (0);
}


/*
 * void
 * es7000_crash()
 *
 * Calling/Exit State:
 *      None.
 *
 */
int
es7000_crash(struct notifier_block *self, unsigned long code, void *unused)
{
        mip_rqst_t *req;
        int rv;
        static hsrf_t rf = { 0x2,
                "es7000_crash: Fatal error detected, system stopping\n"};


        printk("es7000_crash: System Crash, code %lx\n", code);
        req = (mip_rqst_t *)&host_msg[FUNC(HB_ENABLE)];

        if (req->state & BUSY) {
                printk("es7000_crash: Mip didn't respond to last request\n") ;
        }

        bzero(req, sizeof(mip_rqst_t));
        req->state |= BUSY;
        req->host_mip.off_0 = 0x02020f;
        req->host_mip.off_8 = 0;
        req->host_mip.off_38 = VALID;
        printk("ES7000: Heartbeat Disable sent\n");
        if ((rv = es7000_mipsend((mbox_t *)req)) != 0) {
                printk("ES7000: HEARTBEAT DISABLE failed: rv: %d\n", rv);
        }
        req->state &= ~BUSY;


        /*
         * Inform the system is going into PANIC XXX
         */
        req = (mip_rqst_t *)&host_msg[FUNC(REPORT_FAULT_OS)];
        bzero(req, sizeof(mip_rqst_t));
        req->state |= BUSY;
        req->host_mip.off_0 = REPORT_FAULT_OS;
        req->host_mip.off_8 = (unsigned long)__pa(&rf);
        req->host_mip.off_10 = sizeof(hsrf_t);
        req->host_mip.off_18 = 0x8;             /* OS Type 8 = AIX 5L */
        req->host_mip.off_38 = VALID;
        if ((rv = es7000_mipsend((mbox_t *)req)) != 0) {
                printk("ES7000: REPORT FAULT failed: rv: %d\n", rv);
        }
        req->state &= ~BUSY;
        return (0);
}

EXPORT_SYMBOL_NOVERS(es7000_crash);

int es7000_start_cpu(int cpu)
{
        unsigned long vect = 0, psaival = 0;

	if (psai == NULL)
		return -1;

        __cli();
        vect = ((unsigned long)__pa(es7000_trampoline_base)/0x1000) << 16;
        psaival = (0x1000000 | vect | cpu);

        while (*psai & 0x1000000)
                ;

        *psai = psaival;

        __sti();
	return 0;

}

int es7000_stop_cpu(int cpu)
{
	int startup;

	if (psai == NULL)
		return -1;

        __cli();
        startup = (0x1000000 | cpu);

        while ((*psai & 0xff00ffff) != startup)
                ;

	startup = (*psai & 0xff0000) >> 16;
        *psai &= 0xffffff;

        __sti();
	return 0;

}
static struct notifier_block es7000_panic_block = { es7000_crash, NULL, 0 };
static struct notifier_block es7000_reboot_block = { es7000_reboot, NULL, 0 };

void
es7000_register_crash(void)
{
        notifier_chain_register(&panic_notifier_list, &es7000_panic_block);
        register_reboot_notifier(&es7000_reboot_block);
}

int
es7000_get_vect(int *irq)
{
        return(200);
}

void es7000_sw_apic(void)
{
	int mip_status;

	if (es7000_plat == 1 && boot_cpu_data.x86 == 6) {
		printk("ES7000: Please use the boot parameter \"acpi=off\" for this system\n");
		return;
	}

	mip_sequence = 0;
	printk("Enabling ES7000 APIC mode.\n");
	bzero(&es7000_mbox, sizeof(mbox_t));
	es7000_mbox.off_0 = SW_APIC;
	es7000_mbox.off_38 = (VALID);
	while((mip_status = es7000_mipsend(&es7000_mbox)) != 0)
		printk("SW_APIC failed, status = %x\n", mip_status);
	mdelay(1000); // Pause for effect
	phys_cpu_present_map = logical_cpu_present_map;
	return;
}

static int
es7000_read_proc (char *page, char **start, off_t off,
		  int count, int *eof, void *data)
{
  struct mbox mbox;
  unsigned char func;
  struct mip_rqst *req = NULL;
  int i = 0, size = 0;

  es7000_read_mailbox (&mbox, 0);
  if (mbox.off_38 & VALID)
    {
      func = FUNC (mbox.off_0);
      if (! DIRECT (mbox.off_0))
	{
	  if (func <= HOST_CMD_MAX)
	    {
	      req = (mip_rqst_t *) & host_msg[func];
	      if (SEQ (req->host_mip.off_0) >= SEQ (mbox.off_0))
		memcpy (&req->mip_host, &mbox, sizeof (struct mbox));
	      req->state &= ~BUSY;
	    }
	}
      else
	{
	  // Process MIP to HOST message
	  if (func <= MIP_CMD_MAX)
	    req = (mip_rqst_t *) & mip_msg[func];
	  if (func > MIP_CMD_MAX || req->state & BUSY)
	    {
	      struct mip_rqst bad = {};
	      bad.state |= BUSY;
	      memcpy (&bad.host_mip, &mbox, sizeof (struct mbox));
	      bad.host_mip.off_38 = 0x01ffff0000000000LL;
	      es7000_mailit (&bad);
	    }
	}
      es7000_set_status ((mbox.off_38 & ~VALID), 0, OFF_38);
    }

  for (; i < sizeof (struct mbox);)
    {
      size +=
	sprintf (page + size, "%02x ",
		 (unsigned char) (*((char *) &mbox + i++)) & 0xff);
      if (!(i % 16))
	*(page + size - 1) = 0x0a;
    }
  return size;
}

static int
es7000_write_proc (struct file *file, const char *buffer,
		   unsigned long count, void *data)
{
  int i;
  struct mbox mbox;
  unsigned char str[4];
  struct mip_rqst *req;

  for (i = 0; i < sizeof (struct mbox); i++)
    {
      memset (str, 0, sizeof (str));
      sscanf ((buffer + i * 3), "%3c", str);
#define CHAR2NUM(x) ((x < '9')?(x -'0'):(((x < 'a')?(x - 'A'):(x - 'a')) + 10))
      *((unsigned char *) &mbox + i) =
	(unsigned char) ((CHAR2NUM (str[0]) * 16) + CHAR2NUM (str[1]));
#undef CHAR2NUM
    }
  req = (mip_rqst_t *) & host_msg[FUNC (mbox.off_0)];
  if (req->state & BUSY)
    printk ("es7000_write_proc: req was still busy\n");
  req->state |= BUSY;
  memcpy (&req->host_mip, &mbox, sizeof (struct mbox));
  if (es7000_mailit (req) != 0)
    printk ("es7000_write_proc: es7000_mailit failed\n");
  req->state &= ~BUSY;
  return count;
}

void
es7000_proc_init (void)
{
  struct proc_dir_entry *es7000_proc_dir;
  struct proc_dir_entry *entry;

  es7000_proc_dir = proc_mkdir ("es7000", 0);

  entry = create_proc_entry ("mbox", 0600, es7000_proc_dir);
  if (entry)
    {
      entry->nlink = 1;
      entry->data = NULL;
      entry->read_proc = es7000_read_proc;
      entry->write_proc = es7000_write_proc;
    }
}

EXPORT_SYMBOL (es7000_proc_init);

void
es7000_distribute_irqs (void)
{
  unsigned char *dp = (unsigned char *)&(es7000_mbox.off_8);
  unsigned char s = 0, map = 0, value;
  int apicid, i, j;

  if (es7000_plat != 2)
    return;

  for (i = 0; i < NR_CPUS; i++)
    {
      apicid = cpu_present_to_apicid (i);
      if (apicid && apicid < 0x80)
	map |= ((s = ((apicid >> 4) & 0x7)) ? (1u << s) : 1);
    }

  for (i = 0, s = 0; i < 8; i++)
    if (map & (1u << i))
      s++;

  if (s < 2)
    return;

  printk ("Enabling ES7000 IRQ distribution.\n");

  memset (&es7000_mbox, 0, sizeof (mbox_t));
  es7000_mbox.off_0 = SEL_SP_LP;

  for (i = 0, j = 9; i < 8; i++, dp++)
    {
      while (j >= 0)
	{
	  if (--j < 0)
	    j = 8;
	  if (map & (1u << j))
	    {
	      value = (j & 0x7);
	      break;
	    }
	}
      *dp = value;
    }

  es7000_mbox.off_38 = (VALID);
  if (es7000_mipsend (&es7000_mbox))
      printk ("es7000_distribute_irqs: SEL_SP_LP failed\n");
  else
    {
      mdelay (1000);	// Pause for effect
      apic_broadcast_id = APIC_BROADCAST_ID_APIC;
    }
}
