/* Gaisler AMBA Plug&Play bus scanning. Functions
 * ending on _nomem is inteded to be used only during
 * initialization, only registers are used (no ram).
 *
 * (C) Copyright 2007
 * Daniel Hellstrom, Gaisler Research, daniel@gaisler.com
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>
#include <command.h>
#include <ambapp.h>

#if defined(CONFIG_CMD_AMBAPP)
extern void ambapp_print_apb(apbctrl_pp_dev * apb,
			     ambapp_ahbdev * apbmst, int index);
extern void ambapp_print_ahb(ahbctrl_pp_dev * ahb, int index);
extern int ambapp_apb_print;
extern int ambapp_ahb_print;
#endif

static int ambapp_apb_scan(unsigned int vendor,	/* Plug&Play Vendor ID */
			   unsigned int driver,	/* Plug&Play Device ID */
			   ambapp_apbdev * dev,	/* Result(s) is placed here */
			   int index,	/* Index of device to start copying Plug&Play
					 * info into dev
					 */
			   int max_cnt	/* Maximal count that dev can hold, if dev
					 * is NULL function will stop scanning after
					 * max_cnt devices are found.
					 */
    )
{
	int i, cnt = 0;
	unsigned int apbmst_base;
	ambapp_ahbdev apbmst;
	apbctrl_pp_dev *apb;

	if (max_cnt == 0)
		return 0;

	/* Get AMBA APB Master */
	if (ambapp_ahbslv_first(VENDOR_GAISLER, GAISLER_APBMST, &apbmst) != 1) {
		return 0;
	}

	/* Get APB CTRL Plug&Play info area */
	apbmst_base = apbmst.address[0] & LEON3_IO_AREA;
	apb = (apbctrl_pp_dev *) (apbmst_base | LEON3_CONF_AREA);

	for (i = 0; i < LEON3_APB_SLAVES; i++) {
#if defined(CONFIG_CMD_AMBAPP)
		if (ambapp_apb_print && amba_vendor(apb->conf)
		    && amba_device(apb->conf)) {
			ambapp_print_apb(apb, &apbmst, i);
		}
#endif
		if ((amba_vendor(apb->conf) == vendor) &&
		    (amba_device(apb->conf) == driver) && ((index < 0)
							   || (index-- == 0))) {
			/* Convert Plug&Play info into a more readable format */
			cnt++;
			if (dev) {
				dev->irq = amba_irq(apb->conf);
				dev->ver = amba_ver(apb->conf);
				dev->address =
				    (apbmst_base |
				     (((apb->
					bar & 0xfff00000) >> 12))) & (((apb->
									bar &
									0x0000fff0)
								       << 4) |
								      0xfff00000);
				dev++;
			}
			/* found max devices? */
			if (cnt >= max_cnt)
				return cnt;
		}
		/* Get next Plug&Play entry */
		apb++;
	}
	return cnt;
}

unsigned int ambapp_apb_next_nomem(register unsigned int vendor,	/* Plug&Play Vendor ID */
				   register unsigned int driver,	/* Plug&Play Device ID */
				   register int index)
{
	register int i;
	register ahbctrl_pp_dev *apbmst;
	register apbctrl_pp_dev *apb;
	register unsigned int apbmst_base;

	/* APBMST is a AHB Slave */
	apbmst = ambapp_ahb_next_nomem(VENDOR_GAISLER, GAISLER_APBMST, 1, 0);
	if (!apbmst)
		return 0;

	apbmst_base = amba_membar_start(apbmst->bars[0]);
	if (amba_membar_type(apbmst->bars[0]) == AMBA_TYPE_AHBIO)
		apbmst_base = AMBA_TYPE_AHBIO_ADDR(apbmst_base);
	apbmst_base &= LEON3_IO_AREA;

	/* Find the vendor/driver device on the first APB bus */
	apb = (apbctrl_pp_dev *) (apbmst_base | LEON3_CONF_AREA);

	for (i = 0; i < LEON3_APB_SLAVES; i++) {
		if ((amba_vendor(apb->conf) == vendor) &&
		    (amba_device(apb->conf) == driver) && ((index < 0)
							   || (index-- == 0))) {
			/* Convert Plug&Play info info a more readable format */
			return (apbmst_base | (((apb->bar & 0xfff00000) >> 12)))
			    & (((apb->bar & 0x0000fff0) << 4) | 0xfff00000);
		}
		/* Get next Plug&Play entry */
		apb++;
	}
	return 0;
}

/****************************** APB SLAVES ******************************/

int ambapp_apb_count(unsigned int vendor, unsigned int driver)
{
	return ambapp_apb_scan(vendor, driver, NULL, 0, LEON3_APB_SLAVES);
}

int ambapp_apb_first(unsigned int vendor,
		     unsigned int driver, ambapp_apbdev * dev)
{
	return ambapp_apb_scan(vendor, driver, dev, 0, 1);
}

int ambapp_apb_next(unsigned int vendor,
		    unsigned int driver, ambapp_apbdev * dev, int index)
{
	return ambapp_apb_scan(vendor, driver, dev, index, 1);
}

int ambapp_apbs_first(unsigned int vendor,
		      unsigned int driver, ambapp_apbdev * dev, int max_cnt)
{
	return ambapp_apb_scan(vendor, driver, dev, 0, max_cnt);
}

enum {
	AHB_SCAN_MASTER = 0,
	AHB_SCAN_SLAVE = 1
};

/* Scan AMBA Plug&Play bus for AMBA AHB Masters or AHB Slaves
 * for a certain matching Vendor and Device ID.
 *
 * Return number of devices found.
 *
 * Compact edition...
 */
static int ambapp_ahb_scan(unsigned int vendor,	/* Plug&Play Vendor ID */
			   unsigned int driver,	/* Plug&Play Device ID */
			   ambapp_ahbdev * dev,	/* Result(s) is placed here */
			   int index,	/* Index of device to start copying Plug&Play
					 * info into dev
					 */
			   int max_cnt,	/* Maximal count that dev can hold, if dev
					 * is NULL function will stop scanning after
					 * max_cnt devices are found.
					 */
			   int type	/* Selectes what type of devices to scan.
					 * 0=AHB Masters
					 * 1=AHB Slaves
					 */
    )
{
	int i, j, cnt = 0, max_pp_devs;
	unsigned int addr;
	ahbctrl_info *info = (ahbctrl_info *) (LEON3_IO_AREA | LEON3_CONF_AREA);
	ahbctrl_pp_dev *ahb;

	if (max_cnt == 0)
		return 0;

	if (type == 0) {
		max_pp_devs = LEON3_AHB_MASTERS;
		ahb = info->masters;
	} else {
		max_pp_devs = LEON3_AHB_SLAVES;
		ahb = info->slaves;
	}

	for (i = 0; i < max_pp_devs; i++) {
#if defined(CONFIG_CMD_AMBAPP)
		if (ambapp_ahb_print && amba_vendor(ahb->conf) &&
		    amba_device(ahb->conf)) {
			ambapp_print_ahb(ahb, i);
		}
#endif
		if ((amba_vendor(ahb->conf) == vendor) &&
		    (amba_device(ahb->conf) == driver) &&
		    ((index < 0) || (index-- == 0))) {
			/* Convert Plug&Play info info a more readable format */
			cnt++;
			if (dev) {
				dev->irq = amba_irq(ahb->conf);
				dev->ver = amba_ver(ahb->conf);
				dev->userdef[0] = ahb->userdef[0];
				dev->userdef[1] = ahb->userdef[1];
				dev->userdef[2] = ahb->userdef[2];
				for (j = 0; j < 4; j++) {
					addr = amba_membar_start(ahb->bars[j]);
					if (amba_membar_type(ahb->bars[j]) ==
					    AMBA_TYPE_AHBIO)
						addr =
						    AMBA_TYPE_AHBIO_ADDR(addr);
					dev->address[j] = addr;
				}
				dev++;
			}
			/* found max devices? */
			if (cnt >= max_cnt)
				return cnt;
		}
		/* Get next Plug&Play entry */
		ahb++;
	}
	return cnt;
}

unsigned int ambapp_ahb_get_info(ahbctrl_pp_dev * ahb, int info)
{
	register unsigned int ret;

	if (!ahb)
		return 0;

	switch (info) {
	default:
		info = 0;
	case 0:
	case 1:
	case 2:
	case 3:
		/* Get Address from PnP Info */
		ret = amba_membar_start(ahb->bars[info]);
		if (amba_membar_type(ahb->bars[info]) == AMBA_TYPE_AHBIO)
			ret = AMBA_TYPE_AHBIO_ADDR(ret);
		return ret;
	}
	return 0;

}

ahbctrl_pp_dev *ambapp_ahb_next_nomem(register unsigned int vendor,	/* Plug&Play Vendor ID */
				      register unsigned int driver,	/* Plug&Play Device ID */
				      register unsigned int opts,	/* 1=slave, 0=master */
				      register int index)
{
	register ahbctrl_pp_dev *ahb;
	register ahbctrl_info *info =
	    (ahbctrl_info *) (LEON3_IO_AREA | LEON3_CONF_AREA);
	register int i;
	register int max_pp_devs;

	if (opts == 0) {
		max_pp_devs = LEON3_AHB_MASTERS;
		ahb = info->masters;
	} else {
		max_pp_devs = LEON3_AHB_SLAVES;
		ahb = info->slaves;
	}

	for (i = 0; i < max_pp_devs; i++) {
		if ((amba_vendor(ahb->conf) == vendor) &&
		    (amba_device(ahb->conf) == driver) &&
		    ((index < 0) || (index-- == 0))) {
			/* Convert Plug&Play info info a more readable format */
			return ahb;
		}
		/* Get next Plug&Play entry */
		ahb++;
	}
	return 0;
}

/****************************** AHB MASTERS ******************************/
int ambapp_ahbmst_count(unsigned int vendor, unsigned int driver)
{
	/* Get number of devices of this vendor&device ID */
	return ambapp_ahb_scan(vendor, driver, NULL, 0, LEON3_AHB_MASTERS,
			       AHB_SCAN_MASTER);
}

int ambapp_ahbmst_first(unsigned int vendor, unsigned int driver,
			ambapp_ahbdev * dev)
{
	/* find first device of this */
	return ambapp_ahb_scan(vendor, driver, dev, 0, 1, AHB_SCAN_MASTER);
}

int ambapp_ahbmst_next(unsigned int vendor,
		       unsigned int driver, ambapp_ahbdev * dev, int index)
{
	/* find first device of this */
	return ambapp_ahb_scan(vendor, driver, dev, index, 1, AHB_SCAN_MASTER);
}

int ambapp_ahbmsts_first(unsigned int vendor,
			 unsigned int driver, ambapp_ahbdev * dev, int max_cnt)
{
	/* find first device of this */
	return ambapp_ahb_scan(vendor, driver, dev, 0, max_cnt,
			       AHB_SCAN_MASTER);
}

/****************************** AHB SLAVES ******************************/
int ambapp_ahbslv_count(unsigned int vendor, unsigned int driver)
{
	/* Get number of devices of this vendor&device ID */
	return ambapp_ahb_scan(vendor, driver, NULL, 0, LEON3_AHB_SLAVES,
			       AHB_SCAN_SLAVE);
}

int ambapp_ahbslv_first(unsigned int vendor, unsigned int driver,
			ambapp_ahbdev * dev)
{
	/* find first device of this */
	return ambapp_ahb_scan(vendor, driver, dev, 0, 1, AHB_SCAN_SLAVE);
}

int ambapp_ahbslv_next(unsigned int vendor,
		       unsigned int driver, ambapp_ahbdev * dev, int index)
{
	/* find first device of this */
	return ambapp_ahb_scan(vendor, driver, dev, index, 1, AHB_SCAN_SLAVE);
}

int ambapp_ahbslvs_first(unsigned int vendor,
			 unsigned int driver, ambapp_ahbdev * dev, int max_cnt)
{
	/* find first device of this */
	return ambapp_ahb_scan(vendor, driver, dev, 0, max_cnt, AHB_SCAN_SLAVE);
}