/*
 * arch/arm/mach-imx/pcie.c
 *
 * PCIe host controller driver for IMX6 SOCs
 *
 * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved.
 * Copyright (C) 2013 Tim Harvey <tharvey@gateworks.com>
 *
 * Bits taken from arch/arm/mach-dove/pcie.c
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>

#include <asm/signal.h>
#include <asm/mach/pci.h>
#include <asm/sizes.h>

#include "msi.h"

/* PCIe Registers */
#define PCIE_ARB_BASE_ADDR  0x01000000
#define PCIE_ARB_END_ADDR   0x01FFFFFF
#define PCIE_RC_IOBLSSR     0x1c

/* Register Definitions */
#define PRT_LOG_R_BaseAddress 0x700

/* Register DB_R0 */
/* Debug Register 0 */
#define DB_R0 (PRT_LOG_R_BaseAddress + 0x28)
#define DB_R0_RegisterSize 32
#define DB_R0_RegisterResetValue 0x0
#define DB_R0_RegisterResetMask 0xFFFFFFFF
/* End of Register Definition for DB_R0 */

/* Register DB_R1 */
/* Debug Register 1 */
#define DB_R1 (PRT_LOG_R_BaseAddress + 0x2c)
#define DB_R1_RegisterSize 32
#define DB_R1_RegisterResetValue 0x0
#define DB_R1_RegisterResetMask 0xFFFFFFFF
/* End of Register Definition for DB_R1 */

#define PCIE_PL_MSICA		0x820
#define PCIE_PL_MSICUA		0x824
#define PCIE_PL_MSIC_INT	0x828

#define MSIC_INT_EN	0x0
#define MSIC_INT_MASK	0x4
#define MSIC_INT_STATUS	0x8

#define ATU_R_BaseAddress 0x900
#define ATU_VIEWPORT_R (ATU_R_BaseAddress + 0x0)
#define ATU_REGION_CTRL1_R (ATU_R_BaseAddress + 0x4)
#define ATU_REGION_CTRL2_R (ATU_R_BaseAddress + 0x8)
#define ATU_REGION_LOWBASE_R (ATU_R_BaseAddress + 0xC)
#define ATU_REGION_UPBASE_R (ATU_R_BaseAddress + 0x10)
#define ATU_REGION_LIMIT_ADDR_R (ATU_R_BaseAddress + 0x14)
#define ATU_REGION_LOW_TRGT_ADDR_R (ATU_R_BaseAddress + 0x18)
#define ATU_REGION_UP_TRGT_ADDR_R (ATU_R_BaseAddress + 0x1C)

/* IOMUXC */
#define IOMUXC_GPR_BASE_ADDR	0x020E0000
#define IOMUXC_GPR1     (imx_pcie.gpr_base + 0x04)
#define IOMUXC_GPR8     (imx_pcie.gpr_base + 0x20)
#define IOMUXC_GPR12    (imx_pcie.gpr_base + 0x30)
/* GPR1: iomuxc_gpr1_pcie_ref_clk_en(iomuxc_gpr1[16]) */
#define iomuxc_gpr1_pcie_ref_clk_en		(1 << 16)
/* GPR1: iomuxc_gpr1_test_powerdown(iomuxc_gpr1_18) */
#define iomuxc_gpr1_test_powerdown		(1 << 18)
/* GPR12: iomuxc_gpr12_los_level(iomuxc_gpr12[8:4]) */
#define iomuxc_gpr12_los_level			(0x1F << 4)
/* GPR12: iomuxc_gpr12_app_ltssm_enable(iomuxc_gpr12[10]) */
#define iomuxc_gpr12_app_ltssm_enable		(1 << 10)
/* GPR12: iomuxc_gpr12_device_type(iomuxc_gpr12[15:12]) */
#define iomuxc_gpr12_device_type		(0xF << 12)
/* GPR8: iomuxc_gpr8_tx_deemph_gen1(iomuxc_gpr8[5:0]) */
#define iomuxc_gpr8_tx_deemph_gen1		(0x3F << 0)
/* GPR8: iomuxc_gpr8_tx_deemph_gen2_3p5db(iomuxc_gpr8[11:6]) */
#define iomuxc_gpr8_tx_deemph_gen2_3p5db	(0x3F << 6)
/* GPR8: iomuxc_gpr8_tx_deemph_gen2_6db(iomuxc_gpr8[17:12]) */
#define iomuxc_gpr8_tx_deemph_gen2_6db		(0x3F << 12)
/* GPR8: iomuxc_gpr8_tx_swing_full(iomuxc_gpr8[24:18]) */
#define iomuxc_gpr8_tx_swing_full		(0x7F << 18)
/* GPR8: iomuxc_gpr8_tx_swing_low(iomuxc_gpr8[31:25]) */
#define iomuxc_gpr8_tx_swing_low		(0x7F << 25)

/* Registers of PHY */
/* Register PHY_STS_R */
/* PHY Status Register */
#define PHY_STS_R (PRT_LOG_R_BaseAddress + 0x110)

/* Register PHY_CTRL_R */
/* PHY Control Register */
#define PHY_CTRL_R (PRT_LOG_R_BaseAddress + 0x114)

#define SSP_CR_SUP_DIG_MPLL_OVRD_IN_LO 0x0011
/* FIELD: RES_ACK_IN_OVRD [15:15]
// FIELD: RES_ACK_IN [14:14]
// FIELD: RES_REQ_IN_OVRD [13:13]
// FIELD: RES_REQ_IN [12:12]
// FIELD: RTUNE_REQ_OVRD [11:11]
// FIELD: RTUNE_REQ [10:10]
// FIELD: MPLL_MULTIPLIER_OVRD [9:9]
// FIELD: MPLL_MULTIPLIER [8:2]
// FIELD: MPLL_EN_OVRD [1:1]
// FIELD: MPLL_EN [0:0]
*/

#define SSP_CR_SUP_DIG_ATEOVRD 0x0010
/* FIELD: ateovrd_en [2:2]
// FIELD: ref_usb2_en [1:1]
// FIELD: ref_clkdiv2 [0:0]
*/

#define SSP_CR_LANE0_DIG_RX_OVRD_IN_LO 0x1005
/* FIELD: RX_LOS_EN_OVRD [13:13]
// FIELD: RX_LOS_EN [12:12]
// FIELD: RX_TERM_EN_OVRD [11:11]
// FIELD: RX_TERM_EN [10:10]
// FIELD: RX_BIT_SHIFT_OVRD [9:9]
// FIELD: RX_BIT_SHIFT [8:8]
// FIELD: RX_ALIGN_EN_OVRD [7:7]
// FIELD: RX_ALIGN_EN [6:6]
// FIELD: RX_DATA_EN_OVRD [5:5]
// FIELD: RX_DATA_EN [4:4]
// FIELD: RX_PLL_EN_OVRD [3:3]
// FIELD: RX_PLL_EN [2:2]
// FIELD: RX_INVERT_OVRD [1:1]
// FIELD: RX_INVERT [0:0]
*/

#define SSP_CR_LANE0_DIG_RX_ASIC_OUT 0x100D
/* FIELD: LOS [2:2]
// FIELD: PLL_STATE [1:1]
// FIELD: VALID [0:0]
*/

/* control bus bit definition */
#define PCIE_CR_CTL_DATA_LOC 0
#define PCIE_CR_CTL_CAP_ADR_LOC 16
#define PCIE_CR_CTL_CAP_DAT_LOC 17
#define PCIE_CR_CTL_WR_LOC 18
#define PCIE_CR_CTL_RD_LOC 19
#define PCIE_CR_STAT_DATA_LOC 0
#define PCIE_CR_STAT_ACK_LOC 16

#define PCIE_CAP_STRUC_BaseAddress 0x70

/* Register LNK_CAP */
/* PCIE Link cap */
#define LNK_CAP (PCIE_CAP_STRUC_BaseAddress + 0xc)

/* End of Register Definitions */

enum {
	MemRdWr = 0,
	MemRdLk = 1,
	IORdWr = 2,
	CfgRdWr0 = 4,
	CfgRdWr1 = 5
};

struct imx_pcie_port {
	u8			index;
	u8			root_bus_nr;
	void __iomem		*base;
	void __iomem		*dbi_base;
	spinlock_t		conf_lock;

	char			io_space_name[16];
	char			mem_space_name[16];

	struct resource		res[2];
	struct clk *clk;
};

struct imx_pcie_info {
	struct imx_pcie_port imx_pcie_port[1];
	int num_pcie_ports;

	void __iomem *base;
	void __iomem *dbi_base;
	void __iomem *gpr_base;

	unsigned int pcie_pwr_en;
	unsigned int pcie_rst;
	unsigned int pcie_wake_up;
	unsigned int pcie_dis;
};

static struct imx_pcie_info imx_pcie;

static int pcie_phy_cr_read(int addr, int *data);
static int pcie_phy_cr_write(int addr, int data);
static void change_field(int *in, int start, int end, int val);

/* IMX PCIE GPR configure routines */
static inline void imx_pcie_clrset(u32 mask, u32 val, void __iomem *addr)
{
	writel(((readl(addr) & ~mask) | (val & mask)), addr);
}

static int imx_pcie_setup(int nr, struct pci_sys_data *sys)
{
	struct imx_pcie_port *pp;

	if (nr >= imx_pcie.num_pcie_ports)
		return 0;

	pp = &imx_pcie.imx_pcie_port[nr];
	pp->root_bus_nr = sys->busnr;

	/*
	 * IORESOURCE_IO
	 */
	snprintf(pp->io_space_name, sizeof(pp->io_space_name),
		 "PCIe %d I/O", pp->index);
	pp->io_space_name[sizeof(pp->io_space_name) - 1] = 0;
	pp->res[0].name = pp->io_space_name;
	if (pp->index == 0) {
		pp->res[0].start = PCIE_ARB_BASE_ADDR;
		pp->res[0].end = pp->res[0].start + SZ_1M - 1;
	}
	pp->res[0].flags = IORESOURCE_IO;
	if (request_resource(&ioport_resource, &pp->res[0]))
		panic("Request PCIe IO resource failed\n");
	pci_add_resource_offset(&sys->resources, &pp->res[0], sys->io_offset);

	/*
	 * IORESOURCE_MEM
	 */
	snprintf(pp->mem_space_name, sizeof(pp->mem_space_name),
			"PCIe %d MEM", pp->index);
	pp->mem_space_name[sizeof(pp->mem_space_name) - 1] = 0;
	pp->res[1].name = pp->mem_space_name;
	if (pp->index == 0) {
		pp->res[1].start = PCIE_ARB_BASE_ADDR + SZ_1M;
		pp->res[1].end = pp->res[1].start + SZ_16M - SZ_2M - 1;
	}
	pp->res[1].flags = IORESOURCE_MEM;
	if (request_resource(&iomem_resource, &pp->res[1]))
		panic("Request PCIe Memory resource failed\n");
	pci_add_resource_offset(&sys->resources, &pp->res[1], sys->mem_offset);

	return 1;
}

static int imx_pcie_link_up(void __iomem *dbi_base)
{
	/* Check the pcie link up or link down */
	int iterations = 200;
	u32 rc, ltssm, rx_valid, temp;

	do {
		/* link is debug bit 36 debug 1 start in bit 32 */
		rc = readl(dbi_base + DB_R1) & (0x1 << (36 - 32)) ;
		iterations--;
		usleep_range(2000, 3000);

		/* From L0, initiate MAC entry to gen2 if EP/RC supports gen2.
		 * Wait 2ms (LTSSM timeout is 24ms, PHY lock is ~5us in gen2).
		 * If (MAC/LTSSM.state == Recovery.RcvrLock)
		 * && (PHY/rx_valid==0) then pulse PHY/rx_reset. Transition
		 * to gen2 is stuck
		 */
		pcie_phy_cr_read(SSP_CR_LANE0_DIG_RX_ASIC_OUT, &rx_valid);
		ltssm = readl(dbi_base + DB_R0) & 0x3F;
		if ((ltssm == 0x0D) && ((rx_valid & 0x01) == 0)) {
			pr_info("Transition to gen2 is stuck, reset PHY!\n");
			pcie_phy_cr_read(SSP_CR_LANE0_DIG_RX_OVRD_IN_LO, &temp);
			change_field(&temp, 3, 3, 0x1);
			change_field(&temp, 5, 5, 0x1);
			pcie_phy_cr_write(SSP_CR_LANE0_DIG_RX_OVRD_IN_LO,
					0x0028);
			usleep_range(2000, 3000);
			pcie_phy_cr_read(SSP_CR_LANE0_DIG_RX_OVRD_IN_LO, &temp);
			change_field(&temp, 3, 3, 0x0);
			change_field(&temp, 5, 5, 0x0);
			pcie_phy_cr_write(SSP_CR_LANE0_DIG_RX_OVRD_IN_LO,
					0x0000);
		}

		if ((iterations < 0))
			pr_info("link up failed, DB_R0:0x%08x, DB_R1:0x%08x!\n"
					, readl(dbi_base + DB_R0)
					, readl(dbi_base + DB_R1));
	} while (!rc && iterations);

	if (!rc)
		return 0;
	return 1;
}

static void imx_pcie_regions_setup(void __iomem *dbi_base)
{
	unsigned bus;
	unsigned i;
	unsigned untranslated_base = PCIE_ARB_END_ADDR +1 - SZ_1M;
	void __iomem *p = dbi_base + PCIE_PL_MSIC_INT;
	/*
	 * i.MX6 defines 16MB in the AXI address map for PCIe.
	 *
	 * That address space excepted the pcie registers is
	 * split and defined into different regions by iATU,
	 * with sizes and offsets as follows:
	 *
	 * 0x0100_0000 --- 0x010F_FFFF 1MB IORESOURCE_IO
	 * 0x0110_0000 --- 0x01EF_FFFF 14MB IORESOURCE_MEM
	 * 0x01F0_0000 --- 0x01FF_FFFF 1MB Cfg + Registers
	 */

	/* CMD reg:I/O space, MEM space, and Bus Master Enable */
	writel(readl(dbi_base + PCI_COMMAND)
			| PCI_COMMAND_IO
			| PCI_COMMAND_MEMORY
			| PCI_COMMAND_MASTER,
			dbi_base + PCI_COMMAND);

	/* Set the CLASS_REV of RC CFG header to PCI_CLASS_BRIDGE_PCI */
	writel(readl(dbi_base + PCI_CLASS_REVISION)
			| (PCI_CLASS_BRIDGE_PCI << 16),
			dbi_base + PCI_CLASS_REVISION);

	/*
	 * region0-3 outbound used to access target cfg
	 */
	for (bus = 1; bus <= 4; bus++) {
		writel(bus - 1, dbi_base + ATU_VIEWPORT_R);
		writel(untranslated_base, dbi_base + ATU_REGION_LOWBASE_R);
		untranslated_base += (1 << 18);
		if (bus == 4)
			untranslated_base -= (1 << 14); //(remove registers)
		writel(untranslated_base - 1, dbi_base + ATU_REGION_LIMIT_ADDR_R);
		writel(0, dbi_base + ATU_REGION_UPBASE_R);

		writel(bus << 24, dbi_base + ATU_REGION_LOW_TRGT_ADDR_R);
		writel(0, dbi_base + ATU_REGION_UP_TRGT_ADDR_R);
		writel((bus > 1) ? CfgRdWr1 : CfgRdWr0,
				dbi_base + ATU_REGION_CTRL1_R);
		writel((1<<31), dbi_base + ATU_REGION_CTRL2_R);
	}

	writel(MSI_MATCH_ADDR, dbi_base + PCIE_PL_MSICA);
	writel(0, dbi_base + PCIE_PL_MSICUA);
	for (i = 0; i < 8 ; i++) {
		writel(0, p + MSIC_INT_EN);
		writel(0xffffffff, p + MSIC_INT_MASK);
		writel(0xffffffff, p + MSIC_INT_STATUS);
		p += 12;
	}
}

void imx_pcie_mask_irq(unsigned pos, int set)
{
	unsigned mask = 1 << (pos & 0x1f);
	unsigned val, newval;
	void __iomem *p = imx_pcie.dbi_base + PCIE_PL_MSIC_INT + MSIC_INT_MASK + ((pos >> 5) * 12);
	if (pos >= (8 * 32))
		return;
	val = readl(p);
	if (set)
		newval = val | mask;
	else
		newval = val & ~mask;
	if (val != newval)
		writel(newval, p);
}

void imx_pcie_enable_irq(unsigned pos, int set)
{
	unsigned mask = 1 << (pos & 0x1f);
	unsigned val, newval;
	void __iomem *p = imx_pcie.dbi_base + PCIE_PL_MSIC_INT + MSIC_INT_EN + ((pos >> 5) * 12);
	if (pos >= (8 * 32))
		return;
	val = readl(p);
	if (set)
		newval = val | mask;
	else
		newval = val & ~mask;
	if (val != newval)
		writel(newval, p);
	if (set && (val != newval))
		imx_pcie_mask_irq(pos, 0);	/* unmask when enabled */
}

unsigned imx_pcie_msi_pending(unsigned index)
{
	unsigned val, mask;
	void __iomem *p = imx_pcie.dbi_base + PCIE_PL_MSIC_INT + (index * 12);
	if (index >= 8)
		return 0;
	val = readl(p + MSIC_INT_STATUS);
	mask = readl(p + MSIC_INT_MASK);
	val &= ~mask;
	writel(val, p + MSIC_INT_STATUS);
	return val;
}

static char master_abort(struct pci_bus *bus, u32 devfn, int where)
{
	u32 reg;
	void __iomem *dbi_base = imx_pcie.dbi_base;
	int ret = 0;

 	reg = readl(dbi_base + PCIE_RC_IOBLSSR);
	if (reg & 0x71000000) {
		if (reg & 1<<30)
			pr_err("%d:%02d.%d 0x%04x: parity error\n", bus->number, PCI_SLOT(devfn), PCI_FUNC(devfn), where);
		if (reg & 1<<29) {
			pr_err("%d:%02d.%d 0x%04x: master abort\n", bus->number, PCI_SLOT(devfn), PCI_FUNC(devfn), where);
			ret = 1;
		}
		if (reg & 1<<28)
			pr_err("%d:%02d.%d 0x%04x: target abort\n", bus->number, PCI_SLOT(devfn), PCI_FUNC(devfn), where);
		if (reg & 1<<24)
			pr_err("%d:%02d.%d 0x%04x: master data parity error\n", bus->number, PCI_SLOT(devfn), PCI_FUNC(devfn), where);
		writel(reg, dbi_base + PCIE_RC_IOBLSSR);
		udelay(1500); // without this delay subsequent reads through bridge can erroneously return 0???
	}
	return ret;
}

static volatile void *get_cfg_addr(struct pci_bus *bus, u32 devfn, int where)
{
	unsigned busnum;
	void __iomem *base = imx_pcie.base;
	void __iomem *dbi_base = imx_pcie.dbi_base;

	if (!bus->number) {
		if (devfn != 0)
			return 0;
		return (imx_pcie.dbi_base) + (where & 0x0ffc);
	}
	if ((devfn > 0xff) || (bus->number > 15))
		return 0;
	busnum = bus->number - 1;
	if ((busnum < 3) && (devfn <= 3)) {
		return (base) + (busnum << 18) + (devfn << 16) + (where & 0xfffc);
	}
	writel(3, dbi_base + ATU_VIEWPORT_R);
	writel((bus->number << 24) | (devfn << 16),
			dbi_base + ATU_REGION_LOW_TRGT_ADDR_R);
	writel((bus->number > 1) ? CfgRdWr1 : CfgRdWr0,
			dbi_base + ATU_REGION_CTRL1_R);
	return (base) + (3 << 18) + (where & 0xfffc);
}

static int imx_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
			int size, u32 *val)
{
	const volatile void *va_address;
	u32 v;

	if (0)
		pr_info("%s: bus=%x, devfn=%x, where=%x size=%x\n", __func__, bus->number, devfn, where, size);
	va_address = get_cfg_addr(bus, devfn, where);
	if (!va_address) {
		*val = 0xffffffff;
		return PCIBIOS_DEVICE_NOT_FOUND;
	}
	v = readl(va_address);
	if (master_abort(bus, devfn, where)) {
		return PCIBIOS_DEVICE_NOT_FOUND;	
	}
	if (0)
		pr_info("%s: bus=%x, devfn=%x, where=%x size=%x v=%x\n", __func__, bus->number, devfn, where, size, v);
	if (size == 4) {
		*val = v;
	} else if (size == 1) {
		*val = (v >> (8 * (where & 3))) & 0xFF;
	} else if (size == 2) {
		*val = (v >> (8 * (where & 3))) & 0xFFFF;
	} else {
		*val = 0xffffffff;
		return PCIBIOS_BAD_REGISTER_NUMBER;
	}
	return PCIBIOS_SUCCESSFUL;
}

static int imx_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
			int where, int size, u32 val)
{
	volatile void *va_address;
	u32 mask, tmp;

	if (0)
		pr_info("%s: bus=%x, devfn=%x, where=%x size=%x val=%x\n", __func__, bus->number, devfn, where, size, val);
	va_address = get_cfg_addr(bus, devfn, where);
	if (!va_address)
		return PCIBIOS_DEVICE_NOT_FOUND;
	if (size == 4) {
		writel(val, va_address);
		return (master_abort(bus, devfn, where))
			?PCIBIOS_DEVICE_NOT_FOUND:PCIBIOS_SUCCESSFUL;
	}
	if (size == 2)
		mask = ~(0xFFFF << ((where & 0x3) * 8));
	else if (size == 1)
		mask = ~(0xFF << ((where & 0x3) * 8));
	else
		return PCIBIOS_BAD_REGISTER_NUMBER;

	tmp = readl(va_address) & mask;
	tmp |= val << ((where & 0x3) * 8);
	writel(tmp, va_address);
	return (master_abort(bus, devfn, where))
		?PCIBIOS_DEVICE_NOT_FOUND:PCIBIOS_SUCCESSFUL;
}

static struct pci_ops imx_pcie_ops = {
	.read = imx_pcie_rd_conf,
	.write = imx_pcie_wr_conf,
};

signed short irq_map[] = {
	-EINVAL,
	MXC_INT_PCIE_3,		/* int a */
	MXC_INT_PCIE_2,		/* int b */
	MXC_INT_PCIE_1,		/* int c */
	MXC_INT_PCIE_0,		/* int d/MSI */
};

static int imx_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
{
	int val = -EINVAL;
	if (pin <= 4)
		val = irq_map[pin];
	return val;
}

static struct hw_pci imx_pci __initdata = {
	.nr_controllers	= 1,
	.setup		= imx_pcie_setup,
	.ops    = &imx_pcie_ops,
	.map_irq	= imx_pcie_map_irq,
};

/* PHY CR bus acess routines */
static int pcie_phy_cr_ack_polling(int max_iterations, int exp_val)
{
	u32 temp_rd_data, wait_counter = 0;

	do {
		temp_rd_data = readl(imx_pcie.dbi_base + PHY_STS_R);
		temp_rd_data = (temp_rd_data >> PCIE_CR_STAT_ACK_LOC) & 0x1;
		wait_counter++;
	} while ((wait_counter < max_iterations) && (temp_rd_data != exp_val));

	if (temp_rd_data != exp_val)
		return 0 ;
	return 1 ;
}

static int pcie_phy_cr_cap_addr(int addr)
{
	u32 temp_wr_data;
	void __iomem *dbi_base = imx_pcie.dbi_base;

	/* write addr */
	temp_wr_data = addr << PCIE_CR_CTL_DATA_LOC ;
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* capture addr */
	temp_wr_data |= (0x1 << PCIE_CR_CTL_CAP_ADR_LOC);
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* wait for ack */
	if (!pcie_phy_cr_ack_polling(100, 1))
		return 0;

	/* deassert cap addr */
	temp_wr_data = addr << PCIE_CR_CTL_DATA_LOC;
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* wait for ack de-assetion */
	if (!pcie_phy_cr_ack_polling(100, 0))
		return 0 ;

	return 1 ;
}

static int pcie_phy_cr_read(int addr , int *data)
{
	u32 temp_rd_data, temp_wr_data;
	void __iomem *dbi_base = imx_pcie.dbi_base;

	/*  write addr */
	/* cap addr */
	if (!pcie_phy_cr_cap_addr(addr))
		return 0;

	/* assert rd signal */
	temp_wr_data = 0x1 << PCIE_CR_CTL_RD_LOC;
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* wait for ack */
	if (!pcie_phy_cr_ack_polling(100, 1))
		return 0;

	/* after got ack return data */
	temp_rd_data = readl(dbi_base + PHY_STS_R);
	*data = (temp_rd_data & (0xffff << PCIE_CR_STAT_DATA_LOC)) ;

	/* deassert rd signal */
	temp_wr_data = 0x0;
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* wait for ack de-assetion */
	if (!pcie_phy_cr_ack_polling(100, 0))
		return 0 ;

	return 1 ;

}

static int pcie_phy_cr_write(int addr, int data)
{
	u32 temp_wr_data;
	void __iomem *dbi_base = imx_pcie.dbi_base;

	/* write addr */
	/* cap addr */
	if (!pcie_phy_cr_cap_addr(addr))
		return 0 ;

	temp_wr_data = data << PCIE_CR_CTL_DATA_LOC;
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* capture data */
	temp_wr_data |= (0x1 << PCIE_CR_CTL_CAP_DAT_LOC);
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* wait for ack */
	if (!pcie_phy_cr_ack_polling(100, 1))
		return 0 ;

	/* deassert cap data */
	temp_wr_data = data << PCIE_CR_CTL_DATA_LOC;
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* wait for ack de-assetion */
	if (!pcie_phy_cr_ack_polling(100, 0))
		return 0;

	/* assert wr signal */
	temp_wr_data = 0x1 << PCIE_CR_CTL_WR_LOC;
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* wait for ack */
	if (!pcie_phy_cr_ack_polling(100, 1))
		return 0;

	/* deassert wr signal */
	temp_wr_data = data << PCIE_CR_CTL_DATA_LOC;
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	/* wait for ack de-assetion */
	if (!pcie_phy_cr_ack_polling(100, 0))
		return 0;

	temp_wr_data = 0x0 ;
	writel(temp_wr_data, dbi_base + PHY_CTRL_R);

	return 1 ;
}

static void change_field(int *in, int start, int end, int val)
{
	int mask;

	mask = ((0xFFFFFFFF << start) ^ (0xFFFFFFFF << (end + 1))) & 0xFFFFFFFF;
	*in = (*in & ~mask) | (val << start);
}

static int imx_pcie_enable_controller(struct device *dev)
{
	struct clk *clk;
	struct device_node *np = dev->of_node;

	if (gpio_is_valid(imx_pcie.pcie_pwr_en)) {
		/* Enable PCIE power */
		gpio_request(imx_pcie.pcie_pwr_en, "PCIE POWER_EN");

		/* activate PCIE_PWR_EN */
		gpio_direction_output(imx_pcie.pcie_pwr_en, 1);
	}

	// power up PCIe PHY
	imx_pcie_clrset(iomuxc_gpr1_test_powerdown, 0 << 18, IOMUXC_GPR1);

	/* enable the clks */
	if (np)
		clk = of_clk_get(np, 0);
	else
		clk = devm_clk_get(dev, "pcie_clk");
	if (IS_ERR(clk)) {
		pr_err("no pcie clock.\n");
		return -EINVAL;
	}

	if (clk_prepare_enable(clk)) {
		pr_err("can't enable pcie clock.\n");
		clk_put(clk);
		return -EINVAL;
	}

	// Enable PCIe PHY ref clock
	imx_pcie_clrset(iomuxc_gpr1_pcie_ref_clk_en, 1 << 16, IOMUXC_GPR1);

	return 0;
}

static void card_reset(struct device *dev)
{
	if (gpio_is_valid(imx_pcie.pcie_rst)) {
		/* PCIE RESET */
		gpio_request(imx_pcie.pcie_rst, "PCIE RESET");

		/* activate PERST_B */
		gpio_direction_output(imx_pcie.pcie_rst, 0);

		/* Add one reset to the pcie external device */
		msleep(100);

		/* deactive PERST_B */
		gpio_direction_output(imx_pcie.pcie_rst, 1);
	}
}

static void add_pcie_port(struct platform_device *pdev, void __iomem *base, void __iomem *dbi_base)
{
	struct clk *clk;
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;

	if (imx_pcie_link_up(dbi_base)) {
		struct imx_pcie_port *pp = &imx_pcie.imx_pcie_port[imx_pcie.num_pcie_ports++];

		pr_info("IMX PCIe port: link up.\n");

		pp->index = 0;
		pp->root_bus_nr = -1;
		pp->base = base;
		pp->dbi_base = dbi_base;
		spin_lock_init(&pp->conf_lock);
		memset(pp->res, 0, sizeof(pp->res));
	} else {
		pr_info("IMX PCIe port: link down!\n");
		/* Release the clocks, and disable the power */

		if (np)
			clk = of_clk_get(np, 0);
		else
			clk = clk_get(NULL, "pcie_clk");
		if (IS_ERR(clk)) {
			pr_err("no pcie clock.\n");
			return;
		}

		clk_disable_unprepare(clk);
		clk_put(clk);

		// Disable the PCIE PHY Ref Clock
		imx_pcie_clrset(iomuxc_gpr1_pcie_ref_clk_en, 0 << 16, IOMUXC_GPR1);

		if (gpio_is_valid(imx_pcie.pcie_pwr_en)) {
			/* Disable PCIE power */
			gpio_request(imx_pcie.pcie_pwr_en, "PCIE POWER_EN");

			/* activate PCIE_PWR_EN */
			gpio_direction_output(imx_pcie.pcie_pwr_en, 0);
		}

		// Power down PCIE PHY
		imx_pcie_clrset(iomuxc_gpr1_test_powerdown, 1 << 18, IOMUXC_GPR1);
	}
}

static int imx_pcie_abort_handler(unsigned long addr, unsigned int fsr,
		struct pt_regs *regs)
{
	unsigned long instr;
	unsigned long pc = instruction_pointer(regs) - 4;

	instr = *(unsigned long *)pc;
/* imprecise aborts are no longer enabled in 3.7+ during init it would appear.
 * We now using PCIE_RC_IOBLSSR to detect master abort however we will still get
 * at least one imprecise abort and need to have a handler.
 */
#if 0
	if (instr == 0xf57ff04f) {
		/* dsb sy */
		pc -= 4;
		instr = *(unsigned long *)pc;
	}
	pr_info("PCIe abort: address = 0x%08lx fsr = 0x%03x PC = 0x%08lx LR = 0x%08lx instr=%08lx\n",
		addr, fsr, regs->ARM_pc, regs->ARM_lr, instr);


	/*
	 * If the instruction being executed was a read,
	 * make it look like it read all-ones.
	 */
	if ((instr & 0x0c500000) == 0x04100000) {
		/* LDR instruction */
		int reg = (instr >> 12) & 15;

		regs->uregs[reg] = -1;
		regs->ARM_pc = pc + 4;
		return 0;
	}
	return 1;
#else
	pr_info("PCIe abort: address = 0x%08lx fsr = 0x%03x PC = 0x%08lx LR = 0x%08lx instr=%08lx\n",
		addr, fsr, regs->ARM_pc, regs->ARM_lr, instr);

	return 0;
#endif
}


static int imx_pcie_pltfm_probe(struct platform_device *pdev)
{
	struct resource *mem;
	struct device *dev = &pdev->dev;
	struct device_node *np = pdev->dev.of_node;
	struct resource res;
	int ret;

	if (!np) {
		dev_err(&pdev->dev, "No of data found\n");
		return -EINVAL;
	}

	res.start = res.end = 0;
	ret = of_address_to_resource(np, 0, &res);
	if (ret)
		goto err;
	mem = &res;
	imx_pcie.pcie_pwr_en = of_get_named_gpio(np, "pwren-gpios", 0);
	imx_pcie.pcie_rst = of_get_named_gpio(np, "rst-gpios", 0);
	imx_pcie.pcie_wake_up = of_get_named_gpio(np, "wake-gpios", 0);
	imx_pcie.pcie_dis = of_get_named_gpio(np, "dis-gpios", 0);
	//pdev->dev.platform_data = pdata;

	imx_pcie.base = ioremap_nocache(PCIE_ARB_END_ADDR - SZ_1M + 1, SZ_1M - SZ_16K);
	if (!imx_pcie.base) {
		pr_err("error with ioremap in function %s\n", __func__);
		return -EIO;
	}

	imx_pcie.dbi_base = devm_ioremap(dev, mem->start, resource_size(mem));
	if (!imx_pcie.dbi_base) {
		dev_err(dev, "can't map %pR\n", mem);
		return -ENOMEM;
	}

	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc-gpr");
	if (!np) {
		dev_err(dev, "can't find iomux\n");
		return -ENOMEM;
	}
	ret = of_address_to_resource(np, 0, &res);
	of_node_put(np);
	if (ret)
		goto err;
	mem = &res;
	imx_pcie.gpr_base = devm_ioremap(dev, mem->start, resource_size(mem));
	if (!imx_pcie.gpr_base) {
		dev_err(dev, "can't map %pR\n", mem);
		return -ENOMEM;
	}

	// hold LTSSM in detect state
	imx_pcie_clrset(iomuxc_gpr12_app_ltssm_enable, 0 << 10, IOMUXC_GPR12);

	/* configure constant input signal to the pcie ctrl and phy */
	// set device type to RC (PCI_EXP_TYPE_ROOT_PORT=4 is from pcie_regs.h)
	imx_pcie_clrset(iomuxc_gpr12_device_type, PCI_EXP_TYPE_ROOT_PORT << 12, IOMUXC_GPR12);
	// loss of signal detect sensitivity function - must be 0x9
	imx_pcie_clrset(iomuxc_gpr12_los_level, 9 << 4, IOMUXC_GPR12);
	// not clear what values these should have from RM
	imx_pcie_clrset(iomuxc_gpr8_tx_deemph_gen1, 0 << 0, IOMUXC_GPR8);
	imx_pcie_clrset(iomuxc_gpr8_tx_deemph_gen2_3p5db, 0 << 6, IOMUXC_GPR8);
	imx_pcie_clrset(iomuxc_gpr8_tx_deemph_gen2_6db, 20 << 12, IOMUXC_GPR8);
	imx_pcie_clrset(iomuxc_gpr8_tx_swing_full, 127 << 18, IOMUXC_GPR8);
	imx_pcie_clrset(iomuxc_gpr8_tx_swing_low, 127 << 25, IOMUXC_GPR8);

	/* Enable the pwr, clks and so on */
	ret = imx_pcie_enable_controller(dev);
	if (ret)
		goto err;

	/* togle the external card's reset */
	card_reset(dev) ;

	usleep_range(3000, 4000);
	imx_pcie_regions_setup(imx_pcie.dbi_base);
	usleep_range(3000, 4000);

	/*
	 * Force to GEN1 because of PCIE2USB storage stress tests
	 * would be failed when GEN2 is enabled
	 */
	writel(((readl(imx_pcie.dbi_base + LNK_CAP) & 0xfffffff0) | 0x1),
			imx_pcie.dbi_base + LNK_CAP);

	/* start link up */
	imx_pcie_clrset(iomuxc_gpr12_app_ltssm_enable, 1 << 10, IOMUXC_GPR12);

	hook_fault_code(16 + 6, imx_pcie_abort_handler, SIGBUS, 0,
			"imprecise external abort");

	/* add the pcie port */
	add_pcie_port(pdev, imx_pcie.base, imx_pcie.dbi_base);

	pci_common_init(&imx_pci);

	return 0;

err:
	return ret;
}

static int imx_pcie_pltfm_remove(struct platform_device *pdev)
{
	struct clk *clk;
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);

	/* Release clocks, and disable power  */
	if (np)
		clk = of_clk_get(np, 0);
	else
		clk = devm_clk_get(dev, "pcie_clk");
	if (IS_ERR(clk))
		pr_err("no pcie clock.\n");

	if (clk) {
		clk_disable_unprepare(clk);
		clk_put(clk);
	}

	// disable PCIe PHY clock ref
	imx_pcie_clrset(iomuxc_gpr1_pcie_ref_clk_en, 0 << 16, IOMUXC_GPR1);

	if (gpio_is_valid(imx_pcie.pcie_pwr_en)) {
		/* Disable PCIE power */
		gpio_request(imx_pcie.pcie_pwr_en, "PCIE POWER_EN");

		/* activate PCIE_PWR_EN */
		gpio_direction_output(imx_pcie.pcie_pwr_en, 0);
	}

	// power down PCIe PHY
	imx_pcie_clrset(iomuxc_gpr1_test_powerdown, 1 << 18, IOMUXC_GPR1);

	iounmap(imx_pcie.base);
	iounmap(imx_pcie.dbi_base);
	iounmap(imx_pcie.gpr_base);
	release_mem_region(iomem->start, resource_size(iomem));
	//platform_set_drvdata(pdev, NULL);

	return 0;
}

static const struct of_device_id of_imx_pcie_match[] = {
	{ .compatible = "fsl,pcie" },
	{}
};
MODULE_DEVICE_TABLE(of, of_imx_pcie_match);

static struct platform_driver imx_pcie_pltfm_driver = {
	.driver = {
		.name	= "imx-pcie",
		.owner	= THIS_MODULE,
		.of_match_table = of_imx_pcie_match,
	},
	.probe		= imx_pcie_pltfm_probe,
	.remove		= imx_pcie_pltfm_remove,
};

/*****************************************************************************\
 *                                                                           *
 * Driver init/exit                                                          *
 *                                                                           *
\*****************************************************************************/

static int __init imx_pcie_drv_init(void)
{
	pcibios_min_io = 0;
	pcibios_min_mem = 0;

	return platform_driver_register(&imx_pcie_pltfm_driver);
}

static void __exit imx_pcie_drv_exit(void)
{
	platform_driver_unregister(&imx_pcie_pltfm_driver);
}

//module_init(imx_pcie_drv_init);
//module_exit(imx_pcie_drv_exit);
late_initcall(imx_pcie_drv_init);

MODULE_DESCRIPTION("i.MX PCIE platform driver");
MODULE_LICENSE("GPL v2");