58df34b5e1
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
318 lines
8.4 KiB
C
318 lines
8.4 KiB
C
/*
|
|
* Support for Gemini PCI Controller
|
|
*
|
|
* Copyright (C) 2009 Janos Laube <janos.dev@gmail.com>
|
|
* Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
|
|
*
|
|
* based on SL2312 PCI controller code
|
|
* Storlink (C) 2003
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/gpio.h>
|
|
|
|
#include <asm/mach/pci.h>
|
|
|
|
#include <mach/irqs.h>
|
|
#include <mach/hardware.h>
|
|
|
|
#define GEMINI_PCI_IOSIZE_1M 0x0000
|
|
|
|
#define GEMINI_PCI_PMC 0x40
|
|
#define GEMINI_PCI_PMCSR 0x44
|
|
#define GEMINI_PCI_CTRL1 0x48
|
|
#define GEMINI_PCI_CTRL2 0x4C
|
|
#define GEMINI_PCI_MEM1_BASE_SIZE 0x50
|
|
#define GEMINI_PCI_MEM2_BASE_SIZE 0x54
|
|
#define GEMINI_PCI_MEM3_BASE_SIZE 0x58
|
|
|
|
#define PCI_CTRL2_INTSTS_OFFSET 28
|
|
#define PCI_CTRL2_INTMASK_OFFSET 22
|
|
|
|
#define GEMINI_PCI_DMA_MASK 0xFFF00000
|
|
#define GEMINI_PCI_DMA_MEM1_BASE 0x00000000
|
|
#define GEMINI_PCI_DMA_MEM2_BASE 0x00000000
|
|
#define GEMINI_PCI_DMA_MEM3_BASE 0x00000000
|
|
#define GEMINI_PCI_DMA_MEM1_SIZE 7
|
|
#define GEMINI_PCI_DMA_MEM2_SIZE 6
|
|
#define GEMINI_PCI_DMA_MEM3_SIZE 6
|
|
|
|
#define PCI_CONF_ENABLE (1 << 31)
|
|
#define PCI_CONF_WHERE(r) ((r) & 0xFC)
|
|
#define PCI_CONF_BUS(b) (((b) & 0xFF) << 16)
|
|
#define PCI_CONF_DEVICE(d) (((d) & 0x1F) << 11)
|
|
#define PCI_CONF_FUNCTION(f) (((f) & 0x07) << 8)
|
|
|
|
#define PCI_IOSIZE_REG (IO_ADDRESS(GEMINI_PCI_IO_BASE))
|
|
#define PCI_PROT_REG (IO_ADDRESS(GEMINI_PCI_IO_BASE) + 0x04)
|
|
#define PCI_CTRL_REG (IO_ADDRESS(GEMINI_PCI_IO_BASE) + 0x08)
|
|
#define PCI_SOFTRST_REG (IO_ADDRESS(GEMINI_PCI_IO_BASE) + 0x10)
|
|
#define PCI_CONFIG_REG (IO_ADDRESS(GEMINI_PCI_IO_BASE) + 0x28)
|
|
#define PCI_DATA_REG (IO_ADDRESS(GEMINI_PCI_IO_BASE) + 0x2C)
|
|
|
|
|
|
static DEFINE_SPINLOCK(gemini_pci_lock);
|
|
|
|
static int gemini_pci_read_config(struct pci_bus* bus, unsigned int fn,
|
|
int config, int size, u32* value)
|
|
{
|
|
unsigned long irq_flags;
|
|
|
|
spin_lock_irqsave(&gemini_pci_lock, irq_flags);
|
|
|
|
__raw_writel(PCI_CONF_BUS(bus->number) |
|
|
PCI_CONF_DEVICE(PCI_SLOT(fn)) |
|
|
PCI_CONF_FUNCTION(PCI_FUNC(fn)) |
|
|
PCI_CONF_WHERE(config) |
|
|
PCI_CONF_ENABLE,
|
|
PCI_CONFIG_REG);
|
|
|
|
*value = __raw_readl(PCI_DATA_REG);
|
|
|
|
if (size == 1)
|
|
*value = (*value >> (8 * (config & 3))) & 0xFF;
|
|
else if (size == 2)
|
|
*value = (*value >> (8 * (config & 3))) & 0xFFFF;
|
|
|
|
spin_unlock_irqrestore(&gemini_pci_lock, irq_flags);
|
|
|
|
dev_dbg(&bus->dev,
|
|
"[read] slt: %.2d, fnc: %d, cnf: 0x%.2X, val (%d bytes): 0x%.8X\n",
|
|
PCI_SLOT(fn), PCI_FUNC(fn), config, size, *value);
|
|
|
|
return PCIBIOS_SUCCESSFUL;
|
|
}
|
|
|
|
static int gemini_pci_write_config(struct pci_bus* bus, unsigned int fn,
|
|
int config, int size, u32 value)
|
|
{
|
|
unsigned long irq_flags = 0;
|
|
int ret = PCIBIOS_SUCCESSFUL;
|
|
|
|
dev_dbg(&bus->dev,
|
|
"[write] slt: %.2d, fnc: %d, cnf: 0x%.2X, val (%d bytes): 0x%.8X\n",
|
|
PCI_SLOT(fn), PCI_FUNC(fn), config, size, value);
|
|
|
|
spin_lock_irqsave(&gemini_pci_lock, irq_flags);
|
|
|
|
__raw_writel(PCI_CONF_BUS(bus->number) |
|
|
PCI_CONF_DEVICE(PCI_SLOT(fn)) |
|
|
PCI_CONF_FUNCTION(PCI_FUNC(fn)) |
|
|
PCI_CONF_WHERE(config) |
|
|
PCI_CONF_ENABLE,
|
|
PCI_CONFIG_REG);
|
|
|
|
switch(size) {
|
|
case 4:
|
|
__raw_writel(value, PCI_DATA_REG);
|
|
break;
|
|
case 2:
|
|
__raw_writew(value, PCI_DATA_REG + (config & 3));
|
|
break;
|
|
case 1:
|
|
__raw_writeb(value, PCI_DATA_REG + (config & 3));
|
|
break;
|
|
default:
|
|
ret = PCIBIOS_BAD_REGISTER_NUMBER;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&gemini_pci_lock, irq_flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct pci_ops gemini_pci_ops = {
|
|
.read = gemini_pci_read_config,
|
|
.write = gemini_pci_write_config,
|
|
};
|
|
|
|
static struct resource gemini_pci_resource_io = {
|
|
.name = "PCI I/O Space",
|
|
.start = GEMINI_PCI_IO_BASE,
|
|
.end = GEMINI_PCI_IO_BASE + SZ_1M - 1,
|
|
.flags = IORESOURCE_IO,
|
|
};
|
|
|
|
static struct resource gemini_pci_resource_mem = {
|
|
.name = "PCI Memory Space",
|
|
.start = GEMINI_PCI_MEM_BASE,
|
|
.end = GEMINI_PCI_MEM_BASE + SZ_128M - 1,
|
|
.flags = IORESOURCE_MEM,
|
|
};
|
|
|
|
static int __init gemini_pci_request_resources(struct pci_sys_data *sys)
|
|
{
|
|
if (request_resource(&ioport_resource, &gemini_pci_resource_io))
|
|
goto bad_resources;
|
|
if (request_resource(&iomem_resource, &gemini_pci_resource_mem))
|
|
goto bad_resources;
|
|
|
|
pci_add_resource(&sys->resources, &gemini_pci_resource_io);
|
|
pci_add_resource(&sys->resources, &gemini_pci_resource_mem);
|
|
|
|
return 0;
|
|
|
|
bad_resources:
|
|
pr_err("Gemini PCI: request_resource() failed. "
|
|
"Abort PCI bus enumeration.\n");
|
|
return -1;
|
|
}
|
|
|
|
static int __init gemini_pci_setup(int nr, struct pci_sys_data *sys)
|
|
{
|
|
unsigned int cmd;
|
|
|
|
pcibios_min_io = 0x100;
|
|
pcibios_min_mem = 0;
|
|
|
|
if ((nr > 0) || gemini_pci_request_resources(sys))
|
|
return 0;
|
|
|
|
/* setup I/O space to 1MB size */
|
|
__raw_writel(GEMINI_PCI_IOSIZE_1M, PCI_IOSIZE_REG);
|
|
|
|
/* setup hostbridge */
|
|
cmd = __raw_readl(PCI_CTRL_REG);
|
|
cmd |= PCI_COMMAND_IO;
|
|
cmd |= PCI_COMMAND_MEMORY;
|
|
cmd |= PCI_COMMAND_MASTER;
|
|
__raw_writel(cmd, PCI_CTRL_REG);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static struct pci_bus* __init gemini_pci_scan_bus(int nr, struct pci_sys_data* sys)
|
|
{
|
|
unsigned int reg = 0;
|
|
struct pci_bus* bus = 0;
|
|
|
|
bus = pci_scan_bus(nr, &gemini_pci_ops, sys);
|
|
if (bus) {
|
|
dev_dbg(&bus->dev, "setting up PCI DMA\n");
|
|
reg = (GEMINI_PCI_DMA_MEM1_BASE & GEMINI_PCI_DMA_MASK)
|
|
| (GEMINI_PCI_DMA_MEM1_SIZE << 16);
|
|
gemini_pci_write_config(bus, 0, GEMINI_PCI_MEM1_BASE_SIZE, 4, reg);
|
|
reg = (GEMINI_PCI_DMA_MEM2_BASE & GEMINI_PCI_DMA_MASK)
|
|
| (GEMINI_PCI_DMA_MEM2_SIZE << 16);
|
|
gemini_pci_write_config(bus, 0, GEMINI_PCI_MEM2_BASE_SIZE, 4, reg);
|
|
reg = (GEMINI_PCI_DMA_MEM3_BASE & GEMINI_PCI_DMA_MASK)
|
|
| (GEMINI_PCI_DMA_MEM3_SIZE << 16);
|
|
gemini_pci_write_config(bus, 0, GEMINI_PCI_MEM3_BASE_SIZE, 4, reg);
|
|
}
|
|
|
|
return bus;
|
|
}
|
|
|
|
/* Should work with all boards based on original Storlink EVB */
|
|
static int __init gemini_pci_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
|
|
{
|
|
if (slot < 9 || slot > 12)
|
|
return -1;
|
|
|
|
return PCI_IRQ_BASE + (((slot - 9) + (pin - 1)) & 0x3);
|
|
}
|
|
|
|
static struct hw_pci gemini_hw_pci __initdata = {
|
|
.nr_controllers = 1,
|
|
.setup = gemini_pci_setup,
|
|
.scan = gemini_pci_scan_bus,
|
|
.map_irq = gemini_pci_map_irq,
|
|
};
|
|
|
|
/* we need this for muxed PCI interrupts handling */
|
|
static struct pci_bus bogus_pci_bus;
|
|
|
|
static void gemini_pci_ack_irq(struct irq_data *d)
|
|
{
|
|
unsigned int irq = d->irq;
|
|
unsigned int reg;
|
|
|
|
gemini_pci_read_config(&bogus_pci_bus, 0, GEMINI_PCI_CTRL2, 4, ®);
|
|
reg &= ~(0xF << PCI_CTRL2_INTSTS_OFFSET);
|
|
reg |= 1 << (irq - PCI_IRQ_BASE + PCI_CTRL2_INTSTS_OFFSET);
|
|
gemini_pci_write_config(&bogus_pci_bus, 0, GEMINI_PCI_CTRL2, 4, reg);
|
|
}
|
|
|
|
static void gemini_pci_mask_irq(struct irq_data *d)
|
|
{
|
|
unsigned int irq = d->irq;
|
|
unsigned int reg;
|
|
|
|
gemini_pci_read_config(&bogus_pci_bus, 0, GEMINI_PCI_CTRL2, 4, ®);
|
|
reg &= ~((0xF << PCI_CTRL2_INTSTS_OFFSET)
|
|
| (1 << (irq - PCI_IRQ_BASE + PCI_CTRL2_INTMASK_OFFSET)));
|
|
gemini_pci_write_config(&bogus_pci_bus, 0, GEMINI_PCI_CTRL2, 4, reg);
|
|
}
|
|
|
|
static void gemini_pci_unmask_irq(struct irq_data *d)
|
|
{
|
|
unsigned int irq = d->irq;
|
|
unsigned int reg;
|
|
|
|
gemini_pci_read_config(&bogus_pci_bus, 0, GEMINI_PCI_CTRL2, 4, ®);
|
|
reg &= ~(0xF << PCI_CTRL2_INTSTS_OFFSET);
|
|
reg |= 1 << (irq - PCI_IRQ_BASE + PCI_CTRL2_INTMASK_OFFSET);
|
|
gemini_pci_write_config(&bogus_pci_bus, 0, GEMINI_PCI_CTRL2, 4, reg);
|
|
}
|
|
|
|
static void gemini_pci_irq_handler(struct irq_desc *desc)
|
|
{
|
|
unsigned int pci_irq_no, irq_stat, reg, i;
|
|
|
|
gemini_pci_read_config(&bogus_pci_bus, 0, GEMINI_PCI_CTRL2, 4, ®);
|
|
irq_stat = reg >> PCI_CTRL2_INTSTS_OFFSET;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
if ((irq_stat & (1 << i)) == 0)
|
|
continue;
|
|
|
|
pci_irq_no = PCI_IRQ_BASE + i;
|
|
|
|
BUG_ON(!(irq_desc[pci_irq_no].handle_irq));
|
|
irq_desc[pci_irq_no].handle_irq(&irq_desc[pci_irq_no]);
|
|
}
|
|
}
|
|
|
|
static struct irq_chip gemini_pci_irq_chip = {
|
|
.name = "PCI",
|
|
.irq_ack = gemini_pci_ack_irq,
|
|
.irq_mask = gemini_pci_mask_irq,
|
|
.irq_unmask = gemini_pci_unmask_irq,
|
|
};
|
|
|
|
static int __init gemini_pci_init(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 72; i <= 95; i++)
|
|
gpio_request(i, "PCI");
|
|
|
|
/* initialize our bogus bus */
|
|
dev_set_name(&bogus_pci_bus.dev, "PCI IRQ handler");
|
|
bogus_pci_bus.number = 0;
|
|
|
|
/* mask and clear all interrupts */
|
|
gemini_pci_write_config(&bogus_pci_bus, 0, GEMINI_PCI_CTRL2 + 2, 2,
|
|
0xF000);
|
|
|
|
for (i = PCI_IRQ_BASE; i < PCI_IRQ_BASE + 4; i++) {
|
|
irq_set_chip_and_handler(i, &gemini_pci_irq_chip,
|
|
handle_level_irq);
|
|
}
|
|
|
|
irq_set_chained_handler(IRQ_PCI, gemini_pci_irq_handler);
|
|
|
|
pci_common_init(&gemini_hw_pci);
|
|
|
|
return 0;
|
|
}
|
|
|
|
subsys_initcall(gemini_pci_init);
|