/*
 * Sonics Silicon Backplane
 * Bus scanning
 *
 * Copyright (C) 2005-2007 Michael Buesch <mb@bu3sch.de>
 * Copyright (C) 2005 Martin Langer <martin-langer@gmx.de>
 * Copyright (C) 2005 Stefano Brivio <st3@riseup.net>
 * Copyright (C) 2005 Danny van Dyk <kugelfang@gentoo.org>
 * Copyright (C) 2005 Andreas Jaggi <andreas.jaggi@waterwave.ch>
 * Copyright (C) 2006 Broadcom Corporation.
 *
 * Licensed under the GNU/GPL. See COPYING for details.
 */

#include <linux/jc43/ssb.h>
#include <linux/jc43/ssb_regs.h>
#include <linux/pci.h>
#include <linux/io.h>
#include <linux/delay.h>

#include <pcmcia/cistpl.h>
#include <pcmcia/ds.h>

#include "ssb_private.h"

static u32 scan_read32(struct ssb_bus *bus, u16 offset)
{
	return ioread32(bus->mmio + offset);
}

static u32 scan_read16(struct ssb_bus *bus, u16 offset)
{
	return ioread16(bus->mmio + offset);
}

static void scan_write32(struct ssb_bus *bus, u16 offset, u32 value)
{
	iowrite32(value, bus->mmio + offset);
}

static void scan_write16(struct ssb_bus *bus, u16 offset, u32 value)
{
	iowrite16(value, bus->mmio + offset);
}



/* PCIe */

static u32 jc43_pcie_read(struct ssb_bus *bus, u32 address)
{
	scan_write32(bus, 0x130, address);
	return scan_read32(bus, 0x134);
}

static void jc43_pcie_write(struct ssb_bus *bus, u32 address, u32 data)
{
	scan_write32(bus, 0x130, address);
	scan_write32(bus, 0x134, data);
}

static void jc43_pcie_mdio_set_phy(struct ssb_bus *bus, u8 phy)
{
	const u16 mdio_control = 0x128;
	const u16 mdio_data = 0x12C;
	u32 v;
	int i;

	v = (1 << 30); /* Start of Transaction */
	v |= (1 << 28); /* Write Transaction */
	v |= (1 << 17); /* Turnaround */
	v |= (0x1F << 18);
	v |= (phy << 4);
	scan_write32(bus, mdio_data, v);

	udelay(10);
	for (i = 0; i < 200; i++) {
		v = scan_read32(bus, mdio_control);
		if (v & 0x100 /* Trans complete */)
			break;
		msleep(1);
	}
}

static u16 jc43_pcie_mdio_read(struct ssb_bus *bus, u8 device, u8 address)
{
	const u16 mdio_control = 0x128;
	const u16 mdio_data = 0x12C;
	int max_retries = 10;
	u16 ret = 0;
	u32 v;
	int i;

	v = 0x80; /* Enable Preamble Sequence */
	v |= 0x2; /* MDIO Clock Divisor */
	scan_write32(bus, mdio_control, v);

	if (0 /*pc->dev->id.revision >= 10*/) {
		max_retries = 200;
		jc43_pcie_mdio_set_phy(bus, device);
	}

	v = (1 << 30); /* Start of Transaction */
	v |= (1 << 29); /* Read Transaction */
	v |= (1 << 17); /* Turnaround */
	if (1 /*pc->dev->id.revision < 10*/)
		v |= (u32)device << 22;
	v |= (u32)address << 18;
	scan_write32(bus, mdio_data, v);
	/* Wait for the device to complete the transaction */
	udelay(10);
	for (i = 0; i < max_retries; i++) {
		v = scan_read32(bus, mdio_control);
		if (v & 0x100 /* Trans complete */) {
			udelay(10);
			ret = scan_read32(bus, mdio_data);
			break;
		}
		msleep(1);
	}
	scan_write32(bus, mdio_control, 0);
	return ret;
}

static void jc43_pcie_mdio_write(struct ssb_bus *bus, u8 device,
				u8 address, u16 data)
{
	const u16 mdio_control = 0x128;
	const u16 mdio_data = 0x12C;
	int max_retries = 10;
	u32 v;
	int i;

	v = 0x80; /* Enable Preamble Sequence */
	v |= 0x2; /* MDIO Clock Divisor */
	scan_write32(bus, mdio_control, v);

	if (0 /*pc->dev->id.revision >= 10*/) {
		max_retries = 200;
		jc43_pcie_mdio_set_phy(bus, device);
	}

	v = (1 << 30); /* Start of Transaction */
	v |= (1 << 28); /* Write Transaction */
	v |= (1 << 17); /* Turnaround */
	if (1 /*pc->dev->id.revision < 10*/)
		v |= (u32)device << 22;
	v |= (u32)address << 18;
	v |= data;
	scan_write32(bus, mdio_data, v);
	/* Wait for the device to complete the transaction */
	udelay(10);
	for (i = 0; i < max_retries; i++) {
		v = scan_read32(bus, mdio_control);
		if (v & 0x100 /* Trans complete */)
			break;
		msleep(1);
	}
	scan_write32(bus, mdio_control, 0);
}





void ssb_iounmap(struct ssb_bus *bus)
{
#ifdef CONFIG_SSB_PCIHOST
	pci_iounmap(bus->host_pci, bus->mmio);
#else
	SSB_BUG_ON(1); /* Can't reach this code. */
#endif
	bus->mmio = NULL;
	bus->mapped_device = NULL;
}

static void __iomem *ssb_ioremap(struct ssb_bus *bus,
				 unsigned long baseaddr)
{
	void __iomem *mmio = NULL;

#ifdef CONFIG_SSB_PCIHOST
	mmio = pci_iomap(bus->host_pci, 0, ~0UL);
#else
	SSB_BUG_ON(1); /* Can't reach this code. */
#endif

	return mmio;
}

int ssb_bus_scan(struct ssb_bus *bus,
		 unsigned long baseaddr)
{
	void __iomem *mmio;
	u32 tmp;
	int i;

	static int id_cc = 0;
	static int id_80211 = 1;
	static int id_pcmcia = 2;
	static int id_pcie = 3;

	const u8 serdes_pll_device = 0x1D;
	const u8 serdes_rx_device = 0x1F;

	mmio = ssb_ioremap(bus, baseaddr);
	bus->mmio = mmio;


	ssb_pci_switch_coreidx(bus, id_cc);
	scan_read32(bus, SSB_CHIPCO_CHIPID);
	scan_read32(bus, SSB_IDLOW);
	scan_read32(bus, SSB_IDHIGH);
	scan_read32(bus, SSB_IDHIGH);
	scan_read32(bus, SSB_CHIPCO_CHIPID);

	ssb_pci_switch_coreidx(bus, id_80211);
	scan_read32(bus, SSB_IDHIGH);

	ssb_pci_switch_coreidx(bus, id_pcmcia);
	scan_read32(bus, SSB_IDHIGH);

	ssb_pci_switch_coreidx(bus, id_pcie);
	scan_read32(bus, SSB_IDHIGH);

	ssb_pci_switch_coreidx(bus, id_cc);
	scan_read32(bus, SSB_IDHIGH);
	scan_read32(bus, SSB_CHIPCO_CHIPSTAT);
	scan_read32(bus, SSB_CHIPCO_CAP);
	scan_read32(bus, SSB_CHIPCO_PMU_CAP);

	ssb_pci_switch_coreidx(bus, id_cc);
	scan_read32(bus, SSB_IDHIGH);

	ssb_pci_switch_coreidx(bus, id_80211);
	scan_read32(bus, SSB_IDHIGH);

	ssb_pci_switch_coreidx(bus, id_pcmcia);
	scan_read32(bus, SSB_IDHIGH);

	ssb_pci_switch_coreidx(bus, id_pcie);
	scan_read32(bus, SSB_IDHIGH);

	ssb_pci_switch_coreidx(bus, id_pcie);
	scan_read16(bus, 0x800);
	tmp = scan_read16(bus, 0x280a);
	scan_write16(bus, 0x280a, 0xedbe);

	ssb_pci_switch_coreidx(bus, id_cc);
	scan_write32(bus, SSB_CHIPCO_GPIOPULLUP, 0);
	scan_write32(bus, SSB_CHIPCO_GPIOPULLDOWN, 0);
	tmp = scan_read32(bus, SSB_CHIPCO_PMU_CTL);
	scan_write32(bus, SSB_CHIPCO_PMU_CTL, tmp);
	scan_write32(bus, 0x618, 0xcbb);
	scan_read32(bus, SSB_CHIPCO_GPIOTIMER);
	scan_write32(bus, SSB_CHIPCO_GPIOTIMER, 0x000a005a);
	scan_read32(bus, SSB_CHIPCO_GPIOTIMER);

	ssb_pci_switch_coreidx(bus, id_pcie);
	jc43_pcie_mdio_write(bus, serdes_rx_device, 1 /* Control */,
			((jc43_pcie_read(bus, 0x204) & 0x10) ? 0xC0 : 0x80));
	tmp = jc43_pcie_mdio_read(bus, serdes_pll_device, 1 /* Control */);
	if (tmp & 0x4000)
		jc43_pcie_mdio_write(bus, serdes_pll_device, 1, tmp & ~0x4000);

	ssb_pci_switch_coreidx(bus, id_80211);
	scan_read32(bus, SSB_IMCFGLO);
	scan_write32(bus, SSB_IMCFGLO, 0x00010003);
	scan_write32(bus, SSB_CHIPCO_BCAST_ADDR, 0x00000fd8);
	scan_write32(bus, SSB_CHIPCO_BCAST_DATA, 0x00000000);

	ssb_pci_switch_coreidx(bus, id_80211);
	scan_read32(bus, SSB_IDHIGH);
	scan_read32(bus, 0xf98);
	scan_read32(bus, 0xf98);
	scan_write32(bus, 0xf98, (SSB_TMSLOW_RESET | SSB_TMSLOW_CLOCK | SSB_TMSLOW_FGC));
	scan_read32(bus, 0xf98);
	scan_read32(bus, SSB_TMSHIGH);
	scan_read32(bus, SSB_IMSTATE);
	scan_write32(bus, 0xf98, (SSB_TMSLOW_CLOCK | SSB_TMSLOW_FGC));
	scan_read32(bus, 0xf98);
	scan_write32(bus, 0xf98, (SSB_TMSLOW_CLOCK));
	scan_read32(bus, 0xf98);
	scan_write32(bus, 0x120, 0x04000400);

	ssb_pci_switch_coreidx(bus, id_cc);
	scan_read32(bus, SSB_CHIPCO_CLKCTLST);
	scan_write32(bus, SSB_CHIPCO_CLKCTLST, 0x00010002);
	for (i = 1500; i; i--) {
		tmp = scan_read32(bus, SSB_CHIPCO_CLKCTLST);
		if (tmp & SSB_CHIPCO_CLKCTLST_HAVEHT)
			break;
		udelay(10);
	}

	ssb_iounmap(bus);
	return 1;
}