/*
 * (C) Copyright 2002
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 *
 * 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 <config.h>
#include <ppcboot.h>
#include <asm/io.h>

#include "pci.h"

#include "hardware.h"
#include "pcippc2.h"

static u32	cpc710_mapped_ram;

static inline u32 bdf_to_dev (
  u32			bdf)
{
  return (bdf >> 11) & 0x1f;
}

static inline u32 bdf_to_bus (
  u32			bdf)
{
  return (bdf >> 16) & 0xff;
}

static inline u32 bdf_to_func (
  u32			bdf)
{
  return (bdf >> 8) & 0x07;
}

static inline u32 make_bdf (
  u32			bus,
  u32			dev,
  u32			func)
{
  return (bus << 16) | (dev << 11) | (func << 8);
}

  /* Enable PCI retry timeouts
   */
void cpc710_pci_enable_timeout (void)
{
  out32(BRIDGE(LOCAL, CFGADDR), 0x50000080);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, CFGDATA), 0x32000000);
  iobarrier_rw();

  out32(BRIDGE(CPCI, CFGADDR), 0x50000180);
  iobarrier_rw();
  out32(BRIDGE(CPCI, CFGDATA), 0x32000000);
  iobarrier_rw();
}

void cpc710_pci_init (void)
{
  u32			sdram_size = pcippc2_sdram_size();

  cpc710_mapped_ram = sdram_size < PCI_MEMORY_MAXSIZE ?
                      sdram_size : PCI_MEMORY_MAXSIZE;

    /* Select the local PCI
     */
  out32(REG(CPC0, PCICNFR), 0x80000002);
  iobarrier_rw();

  out32(REG(CPC0, PCIBAR), BRIDGE_LOCAL_PHYS);
  iobarrier_rw();

    /* Enable PCI bridge address decoding
     */
  out32(REG(CPC0, PCIENB), 0x80000000);
  iobarrier_rw();

    /* Select the CPCI bridge
     */
  out32(REG(CPC0, PCICNFR), 0x80000003);
  iobarrier_rw();

  out32(REG(CPC0, PCIBAR), BRIDGE_CPCI_PHYS);
  iobarrier_rw();

    /* Enable PCI bridge address decoding
     */
  out32(REG(CPC0, PCIENB), 0x80000000);
  iobarrier_rw();

    /* Disable configuration accesses
     */
  out32(REG(CPC0, PCICNFR), 0x80000000);
  iobarrier_rw();

    /* Initialise the local PCI
     */
  out32(BRIDGE(LOCAL, CRR), 0x7c000000);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, PCIDG), 0x40000000);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, PIBAR), BRIDGE_LOCAL_IO_BUS);
  out32(BRIDGE(LOCAL, SIBAR), BRIDGE_LOCAL_IO_PHYS);
  out32(BRIDGE(LOCAL, IOSIZE), -BRIDGE_LOCAL_IO_SIZE);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, PMBAR), BRIDGE_LOCAL_MEM_BUS);
  out32(BRIDGE(LOCAL, SMBAR), BRIDGE_LOCAL_MEM_PHYS);
  out32(BRIDGE(LOCAL, MSIZE), -BRIDGE_LOCAL_MEM_SIZE);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, PR), 0x80ffe000);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, ACR), 0xfe000000);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, PSBAR), PCI_MEMORY_BUS >> 24);
  out32(BRIDGE(LOCAL, BARPS), PCI_MEMORY_PHYS >> 24);
  out32(BRIDGE(LOCAL, PSSIZE), 256 - (cpc710_mapped_ram >> 24));
  iobarrier_rw();

    /* Initialise the CPCI bridge
     */
  out32(BRIDGE(CPCI, CRR), 0x7c000000);
  iobarrier_rw();
  out32(BRIDGE(CPCI, PCIDG), 0xC0000000);
  iobarrier_rw();
  out32(BRIDGE(CPCI, PIBAR), BRIDGE_CPCI_IO_BUS);
  out32(BRIDGE(CPCI, SIBAR), BRIDGE_CPCI_IO_PHYS);
  out32(BRIDGE(CPCI, IOSIZE), -BRIDGE_CPCI_IO_SIZE);
  iobarrier_rw();
  out32(BRIDGE(CPCI, PMBAR), BRIDGE_CPCI_MEM_BUS);
  out32(BRIDGE(CPCI, SMBAR), BRIDGE_CPCI_MEM_PHYS);
  out32(BRIDGE(CPCI, MSIZE), -BRIDGE_CPCI_MEM_SIZE);
  iobarrier_rw();
  out32(BRIDGE(CPCI, PR), 0x80ffe000);
  iobarrier_rw();
  out32(BRIDGE(CPCI, ACR), 0xdf000000);
  iobarrier_rw();
  out32(BRIDGE(CPCI, PSBAR), PCI_MEMORY_BUS >> 24);
  out32(BRIDGE(CPCI, BARPS), PCI_MEMORY_PHYS >> 24);
  out32(BRIDGE(CPCI, PSSIZE), 256 - (cpc710_mapped_ram >> 24));
  iobarrier_rw();

    /* Local PCI
     */

  out32(BRIDGE(LOCAL, CFGADDR), 0x04000080);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, CFGDATA), 0x56010000);
  iobarrier_rw();

  out32(BRIDGE(LOCAL, CFGADDR), 0x0c000080);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, CFGDATA), PCI_LATENCY_TIMER << 16);
  iobarrier_rw();
  
    /* Set bus and subbus numbers
     */
  out32(BRIDGE(LOCAL, CFGADDR), 0x40000080);
  iobarrier_rw();
  out32(BRIDGE(LOCAL, CFGDATA), 0x00000000);
  iobarrier_rw();

  out32(BRIDGE(LOCAL, CFGADDR), 0x50000080);
  iobarrier_rw();
    /* PCI retry timeouts will be enabled later
     */
  out32(BRIDGE(LOCAL, CFGDATA), 0x00000000);
  iobarrier_rw();

    /* CPCI
     */

    /* Set bus and subbus numbers
     */
  out32(BRIDGE(CPCI, CFGADDR), 0x40000080);
  iobarrier_rw();
  out32(BRIDGE(CPCI, CFGDATA), 0x01010000);
  iobarrier_rw();

  out32(BRIDGE(CPCI, CFGADDR), 0x04000180);
  iobarrier_rw();
  out32(BRIDGE(CPCI, CFGDATA), 0x56010000);
  iobarrier_rw();

  out32(BRIDGE(CPCI, CFGADDR), 0x0c000180);
  iobarrier_rw();
  out32(BRIDGE(CPCI, CFGDATA), PCI_LATENCY_TIMER << 16);
  iobarrier_rw();

    /* Write to the PSBAR */
  out32(BRIDGE(CPCI, CFGADDR), 0x10000180);
  iobarrier_rw();
  out32(BRIDGE(CPCI, CFGDATA), cpu_to_le32(PCI_MEMORY_BUS));
  iobarrier_rw();

    /* Set bus and subbus numbers
     */
  out32(BRIDGE(CPCI, CFGADDR), 0x40000180);
  iobarrier_rw();
  out32(BRIDGE(CPCI, CFGDATA), 0x01ff0000);
  iobarrier_rw();

  out32(BRIDGE(CPCI, CFGADDR), 0x50000180);
  iobarrier_rw();
  out32(BRIDGE(CPCI, CFGDATA), 0x32000000);
    /* PCI retry timeouts will be enabled later
     */
  out32(BRIDGE(CPCI, CFGDATA), 0x00000000);
  iobarrier_rw();
 
    /* Remove reset on the PCI buses
     */
  out32(BRIDGE(LOCAL, CRR), 0xfc000000);
  iobarrier_rw();
  out32(BRIDGE(CPCI, CRR), 0xfc000000);
  iobarrier_rw();
}

unsigned int PCI_Read_CFG_Reg (
  int			busdevfunc,
  int			reg,
  int			width)
{
  u32			bus = bdf_to_bus(busdevfunc);
  u32			dev = bdf_to_dev(busdevfunc);
  u32			bridge_addr = bus == 0 ? BRIDGE_LOCAL_PHYS : BRIDGE_CPCI_PHYS;
  u32			ret;

    /* Drop out-of-range device numbers
     */
  if (bus >= 2 || dev == 0 || dev > 21)
  {
    return (1 << (8 * width)) - 1;
  }
  
  out32r(bridge_addr + HW_BRIDGE_CFGADDR, busdevfunc | (reg & 0xfc) | 0x80000000);
  iobarrier_rw();

  switch (width)
  {
    case 1:
      ret = in8(bridge_addr + HW_BRIDGE_CFGDATA + (reg & 0x3));
      break;
    case 2:
      ret = in16r(bridge_addr + HW_BRIDGE_CFGDATA + (reg & 0x2));
      break;
    case 4:
      ret = in32r(bridge_addr + HW_BRIDGE_CFGDATA);
      break;
    default:
      puts("Invalid PCI access !\n");
      hang();
        /* To avoid warning
	 */
      ret = 0xffffffff;
  }

  iobarrier_rw();
  
  return ret;
}

int PCI_Write_CFG_Reg (
  int			busdevfunc,
  int			reg,
  unsigned int		value,
  int			width)
{
  u32			bus = bdf_to_bus(busdevfunc);
  u32			dev = bdf_to_dev(busdevfunc);
  u32			bridge_addr = bus == 0 ? BRIDGE_LOCAL_PHYS : BRIDGE_CPCI_PHYS;

    /* Drop out-of-range device numbers
     */
  if (bus >= 2 || dev == 0 || dev > 21)
  {
    return 0;
  }
  
  out32r(bridge_addr + HW_BRIDGE_CFGADDR, busdevfunc | (reg & 0xfc) | 0x80000000);
  iobarrier_rw();

  switch (width)
  {
    case 1:
      out8(bridge_addr + HW_BRIDGE_CFGDATA + (reg & 0x3), value);
      break;
    case 2:
      out16r(bridge_addr + HW_BRIDGE_CFGDATA + (reg & 0x2), value);
      break;
    case 4:
      out32r(bridge_addr + HW_BRIDGE_CFGDATA, value);
      break;
    default:
      puts("Invalid PCI access !\n");
      hang();
  }

  iobarrier_rw();

  return 0;
}

u32 pci_phys_to_io (
  u32 			p)
{
  if (p >= BRIDGE_LOCAL_IO_PHYS &&
      p < BRIDGE_LOCAL_IO_PHYS + BRIDGE_LOCAL_IO_SIZE)
  {
    return p - BRIDGE_LOCAL_IO_PHYS + BRIDGE_LOCAL_IO_BUS;
  }

  if (p >= BRIDGE_CPCI_IO_PHYS &&
      p < BRIDGE_CPCI_IO_PHYS + BRIDGE_CPCI_IO_SIZE)
  {
    return p - BRIDGE_CPCI_IO_PHYS + BRIDGE_CPCI_IO_BUS;
  }

  puts("Address not in the PCI I/O space !\n");
  hang();

  return 0;
}

u32 pci_io_to_phys (
  u32 			i)
{
  if (i >= BRIDGE_LOCAL_IO_BUS &&
      i < BRIDGE_LOCAL_IO_BUS + BRIDGE_LOCAL_IO_SIZE)
  {
    return i - BRIDGE_LOCAL_IO_BUS + BRIDGE_LOCAL_IO_PHYS;
  }

  if (i >= BRIDGE_CPCI_IO_BUS &&
      i < BRIDGE_CPCI_IO_BUS + BRIDGE_CPCI_IO_SIZE)
  {
    return i - BRIDGE_CPCI_IO_BUS + BRIDGE_CPCI_IO_PHYS;
  }

  puts("Address not in the PCI I/O space !\n");
  hang();

  return 0;
}

u32 pci_phys_to_mem (
  u32 			p)
{
  if (p >= BRIDGE_LOCAL_MEM_PHYS &&
      p < BRIDGE_LOCAL_MEM_PHYS + BRIDGE_LOCAL_MEM_SIZE)
  {
    return p - BRIDGE_LOCAL_MEM_PHYS + BRIDGE_LOCAL_MEM_BUS;
  }

  if (p >= BRIDGE_CPCI_MEM_PHYS &&
      p < BRIDGE_CPCI_MEM_PHYS + BRIDGE_CPCI_MEM_SIZE)
  {
    return p - BRIDGE_CPCI_MEM_PHYS + BRIDGE_CPCI_MEM_BUS;
  }

  puts("Address not in the PCI memory space !\n");
  hang();

  return 0;
}

u32 pci_mem_to_phys (
  u32 			m)
{
  if (m >= BRIDGE_LOCAL_MEM_BUS &&
      m < BRIDGE_LOCAL_MEM_BUS + BRIDGE_LOCAL_MEM_SIZE)
  {
    return m - BRIDGE_LOCAL_MEM_BUS + BRIDGE_LOCAL_MEM_PHYS;
  }

  if (m >= BRIDGE_CPCI_MEM_BUS &&
      m < BRIDGE_CPCI_MEM_BUS + BRIDGE_CPCI_MEM_SIZE)
  {
    return m - BRIDGE_CPCI_MEM_BUS + BRIDGE_CPCI_MEM_PHYS;
  }

  puts("Address not in the PCI memory space !\n");
  hang();

  return 0;
}

u32 pci_ram_to_mem (
  u32 			r)
{
  if (r >= PCI_MEMORY_PHYS &&
      r < PCI_MEMORY_PHYS + cpc710_mapped_ram)
  {
    return r - PCI_MEMORY_PHYS + PCI_MEMORY_BUS;
  }

  puts("Address not in the RAM space !\n");
  hang();

  return 0;
}

u32 pci_mem_to_ram (
  u32 			p)
{
  if (p >= PCI_MEMORY_BUS &&
      p < PCI_MEMORY_BUS + cpc710_mapped_ram)
  {
    return p - PCI_MEMORY_BUS + PCI_MEMORY_PHYS;
  }

  puts("Address not in the PCI RAM memory space !\n");
  hang();

  return 0;
}
