/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Starfire Memory Controller specific routines.
 */

#include <sys/debug.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/dditypes.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/ddi_impldefs.h>
#include <sys/promif.h>
#include <sys/machsystm.h>

#include <sys/starfire.h>

struct mc_dimm_table {
	int	mc_type;
	int	mc_module_size;		/* module size in MB */
};

static struct mc_dimm_table dimmsize_table[] = {
	{ 4,	8 },
	{ 6,	8 },
	{ 11,	32 },
	{ 15,	128 },
	{ 0,	0 }
};

#define	MC_MB(mb) ((mb) * 1048576ull)

struct mc_seg_size {
	uint_t		seg_mask;
	uint64_t	seg_size;
};

struct mc_seg_size mc_seg_table[] = {
	{ 0x7f,	MC_MB(64)	},
	{ 0x7e,	MC_MB(128)	},
	{ 0x7c,	MC_MB(256)	},
	{ 0x78,	MC_MB(512)	},
	{ 0x70,	MC_MB(1024)	},
	{ 0x60,	MC_MB(2048)	},
	{ 0x40,	MC_MB(4096)	},
	{ 0,	0		}
};

/*
 * Alignment of memory between MC's.
 */
uint64_t
mc_get_mem_alignment()
{
	return (STARFIRE_MC_MEMBOARD_ALIGNMENT);
}

uint64_t
mc_get_asr_addr(pnode_t nodeid)
{
	int		rlen;
	uint64_t	psi_addr;
	struct sf_memunit_regspec	reg;

	rlen = prom_getproplen(nodeid, "reg");
	if (rlen != sizeof (struct sf_memunit_regspec))
		return ((uint64_t)-1);

	if (prom_getprop(nodeid, "reg", (caddr_t)&reg) < 0)
		return ((uint64_t)-1);

	psi_addr = ((uint64_t)reg.regspec_addr_hi) << 32;
	psi_addr |= (uint64_t)reg.regspec_addr_lo;

	return (STARFIRE_MC_ASR_ADDR(psi_addr));
}

uint64_t
mc_get_idle_addr(pnode_t nodeid)
{
	int		rlen;
	uint64_t	psi_addr;
	struct sf_memunit_regspec	reg;

	rlen = prom_getproplen(nodeid, "reg");
	if (rlen != sizeof (struct sf_memunit_regspec))
		return ((uint64_t)-1);

	if (prom_getprop(nodeid, "reg", (caddr_t)&reg) < 0)
		return ((uint64_t)-1);

	psi_addr = ((uint64_t)reg.regspec_addr_hi) << 32;
	psi_addr |= (uint64_t)reg.regspec_addr_lo;

	return (STARFIRE_MC_IDLE_ADDR(psi_addr));
}

int
mc_get_dimm_size(pnode_t nodeid)
{
	uint64_t	psi_addr;
	uint_t		dimmtype;
	int		i, rlen;
	struct sf_memunit_regspec	reg;

	rlen = prom_getproplen(nodeid, "reg");
	if (rlen != sizeof (struct sf_memunit_regspec))
		return (-1);

	if (prom_getprop(nodeid, "reg", (caddr_t)&reg) < 0)
		return (-1);

	psi_addr = ((uint64_t)reg.regspec_addr_hi) << 32;
	psi_addr |= (uint64_t)reg.regspec_addr_lo;
	psi_addr = STARFIRE_MC_DIMMTYPE_ADDR(psi_addr);

	if (psi_addr == (uint64_t)-1)
		return (-1);

	dimmtype = ldphysio(psi_addr);
	dimmtype &= STARFIRE_MC_DIMMSIZE_MASK;

	for (i = 0; dimmsize_table[i].mc_type != 0; i++)
		if (dimmsize_table[i].mc_type == dimmtype)
			break;

	return (dimmsize_table[i].mc_module_size);
}

uint64_t
mc_get_alignment_mask(pnode_t nodeid)
{
	uint64_t	psi_addr, seg_sz;
	uint_t		mcreg, seg_sz_mask;
	int		i, rlen;
	struct sf_memunit_regspec	reg;

	rlen = prom_getproplen(nodeid, "reg");
	if (rlen != sizeof (struct sf_memunit_regspec))
		return (-1);

	if (prom_getprop(nodeid, "reg", (caddr_t)&reg) < 0)
		return (-1);

	psi_addr = ((uint64_t)reg.regspec_addr_hi) << 32;
	psi_addr |= (uint64_t)reg.regspec_addr_lo;
	psi_addr = STARFIRE_MC_ASR_ADDR(psi_addr);

	if (psi_addr == (uint64_t)-1)
		return (-1);

	mcreg = ldphysio(psi_addr);
	seg_sz_mask = (mcreg & STARFIRE_MC_MASK_MASK) >> 8;

	for (i = 0; mc_seg_table[i].seg_size != 0; i++)
		if (mc_seg_table[i].seg_mask == seg_sz_mask)
			break;

	if (mc_seg_table[i].seg_size == 0)
		seg_sz = mc_get_mem_alignment();
	else
		seg_sz = mc_seg_table[i].seg_size;

#ifdef DEBUG
	printf("nodeid %x, mc asr addr %lx, val %x, seg_sz_mask %x, "
	    "seg_sz %lx\n", nodeid, psi_addr, mcreg, seg_sz_mask, seg_sz);
#endif /* DEBUG */

	return (seg_sz - 1);
}

int
mc_read_asr(pnode_t nodeid, uint_t *mcregp)
{
	uint64_t	psi_addr;

	*mcregp = 0;

	psi_addr = mc_get_asr_addr(nodeid);
	if (psi_addr == (uint64_t)-1)
		return (-1);

	*mcregp = ldphysio(psi_addr);

	return (0);
}

int
mc_write_asr(pnode_t nodeid, uint_t mcreg)
{
	uint_t		mcreg_rd;
	uint64_t	psi_addr;

	psi_addr = mc_get_asr_addr(nodeid);
	if (psi_addr == (uint64_t)-1)
		return (-1);

	stphysio(psi_addr, mcreg);

	mcreg_rd = ldphysio(psi_addr);
	ASSERT(mcreg_rd == mcreg);

	return ((mcreg_rd != mcreg) ? -1 : 0);
}

uint64_t
mc_asr_to_pa(uint_t mcreg)
{
	uint64_t	pa, masr, addrmask, lowbitmask;

	/*
	 * Remove memory present bit.
	 */
	masr = (uint64_t)(mcreg & ~STARFIRE_MC_MEM_PRESENT_MASK);
	/*
	 * Get mask for bits 32-26.
	 */
	lowbitmask = masr & (uint64_t)STARFIRE_MC_MASK_MASK;
	lowbitmask <<= STARFIRE_MC_MASK_SHIFT;
	addrmask = STARFIRE_MC_ADDR_HIBITS | lowbitmask;

	pa = (masr << STARFIRE_MC_BASE_SHIFT) & addrmask;

	return (pa);
}

uint_t
mc_pa_to_asr(uint_t masr, uint64_t pa)
{
	uint64_t	addrmask, lowbitmask;
	uint_t		base;

	/*
	 * Get mask for bits 32-26.
	 */
	lowbitmask = masr & (uint64_t)STARFIRE_MC_MASK_MASK;
	lowbitmask <<= STARFIRE_MC_MASK_SHIFT;
	addrmask = STARFIRE_MC_ADDR_HIBITS | lowbitmask;

	base  = (pa & addrmask) >> STARFIRE_MC_BASE_SHIFT;
	masr &= ~ STARFIRE_MC_MEM_BASEADDR_MASK;
	masr |= base & STARFIRE_MC_MEM_BASEADDR_MASK;

	ASSERT(mc_asr_to_pa(masr) == pa);

	return (masr);
}