![Linus Torvalds](/assets/img/avatar_default.png)
The new clock subsystem was merged in linux-3.4 without any users, this now moves the first three platforms over to it: imx, mxs and spear. The series also contains the changes for the clock subsystem itself, since Mike preferred to have it together with the platforms that require these changes, in order to avoid interdependencies and conflicts. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) iQIcBAABAgAGBQJPuexPAAoJEIwa5zzehBx3YBsP/0nFhXjb5t1PdLfFzGKtcZVB j4zXWXMHQ1fA7wIfEpZF3Nnco6MQkufF5wJPoPdn1+wmkzCn3D6IwNVWVtW4U5i9 VGyShSbgusAAYXUe/9yYj8eN+bbRQSvdN4eWYWU6+rRXShGZ5dZZmp+IPNl54dnW 6F8uCnHX0cnIMCpGqV+41zZgZ/4wL2k9gdqu0LO6pi07o4tGd0Z4gcySgUFAnn1R kofNHueYIP4UgOg8DREoBzVKlpRqMou3S2kSZUfMeb3Q9ryF7UIvaGqIILyi7PKL kWd3nptg0EPavfL21SwXHiGpnDpB/Gj/F70kcPLus5RYujB24C9bvBmc26z68NZx Sz9mbElkkIU5duZsl1nxBWJ8IZ/tSWdtmC2xQMznmV7gHyGgVwr4j47f4Uv5sBvM 14JHDO7mqN6E6FnTFZu/oPAN5pDjgL+TVNK5BU6Wkq0zitrA6eyKDqCvBCqkO6Nn tNzOuyRDzMOwM7HzqXhxqtzJWXylO1Mldc4bM8X4Cocf4pnLna/X6uP6dgE6A+JY azVYx4I/0NdEPerDTzIcEhBDgZeBVROhUQr+kHxc4rf6WzUUbu/wEo1UKXWV66oW 1jb1yAFFWqYjkQuQc2PD4JSx35sFJaoSaoneRtmzBzRDfzSr5KjKj1E0e1skyMFq 7ZVLCqZD0cB9DhmMDkWP =rwFF -----END PGP SIGNATURE----- Merge tag 'clock' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc Pull arm-soc clock driver changes from Olof Johansson: "The new clock subsystem was merged in linux-3.4 without any users, this now moves the first three platforms over to it: imx, mxs and spear. The series also contains the changes for the clock subsystem itself, since Mike preferred to have it together with the platforms that require these changes, in order to avoid interdependencies and conflicts." Fix up trivial conflicts in arch/arm/mach-kirkwood/common.c (code removed in one branch, added OF support in another) and drivers/dma/imx-sdma.c (independent changes next to each other). * tag 'clock' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (97 commits) clk: Fix CLK_SET_RATE_GATE flag validation in clk_set_rate(). clk: Provide dummy clk_unregister() SPEAr: Update defconfigs SPEAr: Add SMI NOR partition info in dts files SPEAr: Switch to common clock framework SPEAr: Call clk_prepare() before calling clk_enable SPEAr: clk: Add General Purpose Timer Synthesizer clock SPEAr: clk: Add Fractional Synthesizer clock SPEAr: clk: Add Auxiliary Synthesizer clock SPEAr: clk: Add VCO-PLL Synthesizer clock SPEAr: Add DT bindings for SPEAr's timer ARM i.MX: remove now unused clock files ARM: i.MX6: implement clocks using common clock framework ARM i.MX35: implement clocks using common clock framework ARM i.MX5: implement clocks using common clock framework ARM: Kirkwood: Replace clock gating ARM: Orion: Audio: Add clk/clkdev support ARM: Orion: PCIE: Add support for clk ARM: Orion: XOR: Add support for clk ARM: Orion: CESA: Add support for clk ...
300 lines
7 KiB
C
300 lines
7 KiB
C
/*
|
|
* arch/arm/mach-kirkwood/pcie.c
|
|
*
|
|
* PCIe functions for Marvell Kirkwood SoCs
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/clk.h>
|
|
#include <video/vga.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/mach/pci.h>
|
|
#include <plat/pcie.h>
|
|
#include <mach/bridge-regs.h>
|
|
#include <plat/addr-map.h>
|
|
#include "common.h"
|
|
|
|
static void kirkwood_enable_pcie_clk(const char *port)
|
|
{
|
|
struct clk *clk;
|
|
|
|
clk = clk_get_sys("pcie", port);
|
|
if (IS_ERR(clk)) {
|
|
printk(KERN_ERR "PCIE clock %s missing\n", port);
|
|
return;
|
|
}
|
|
clk_prepare_enable(clk);
|
|
clk_put(clk);
|
|
}
|
|
|
|
/* This function is called very early in the boot when probing the
|
|
hardware to determine what we actually are, and what rate tclk is
|
|
ticking at. Hence calling kirkwood_enable_pcie_clk() is not
|
|
possible since the clk tree has not been created yet. */
|
|
void kirkwood_enable_pcie(void)
|
|
{
|
|
u32 curr = readl(CLOCK_GATING_CTRL);
|
|
if (!(curr & CGC_PEX0))
|
|
writel(curr | CGC_PEX0, CLOCK_GATING_CTRL);
|
|
}
|
|
|
|
void kirkwood_pcie_id(u32 *dev, u32 *rev)
|
|
{
|
|
kirkwood_enable_pcie();
|
|
*dev = orion_pcie_dev_id((void __iomem *)PCIE_VIRT_BASE);
|
|
*rev = orion_pcie_rev((void __iomem *)PCIE_VIRT_BASE);
|
|
}
|
|
|
|
struct pcie_port {
|
|
u8 root_bus_nr;
|
|
void __iomem *base;
|
|
spinlock_t conf_lock;
|
|
int irq;
|
|
struct resource res[2];
|
|
};
|
|
|
|
static int pcie_port_map[2];
|
|
static int num_pcie_ports;
|
|
|
|
static int pcie_valid_config(struct pcie_port *pp, int bus, int dev)
|
|
{
|
|
/*
|
|
* Don't go out when trying to access --
|
|
* 1. nonexisting device on local bus
|
|
* 2. where there's no device connected (no link)
|
|
*/
|
|
if (bus == pp->root_bus_nr && dev == 0)
|
|
return 1;
|
|
|
|
if (!orion_pcie_link_up(pp->base))
|
|
return 0;
|
|
|
|
if (bus == pp->root_bus_nr && dev != 1)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* PCIe config cycles are done by programming the PCIE_CONF_ADDR register
|
|
* and then reading the PCIE_CONF_DATA register. Need to make sure these
|
|
* transactions are atomic.
|
|
*/
|
|
|
|
static int pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
|
|
int size, u32 *val)
|
|
{
|
|
struct pci_sys_data *sys = bus->sysdata;
|
|
struct pcie_port *pp = sys->private_data;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
if (pcie_valid_config(pp, bus->number, PCI_SLOT(devfn)) == 0) {
|
|
*val = 0xffffffff;
|
|
return PCIBIOS_DEVICE_NOT_FOUND;
|
|
}
|
|
|
|
spin_lock_irqsave(&pp->conf_lock, flags);
|
|
ret = orion_pcie_rd_conf(pp->base, bus, devfn, where, size, val);
|
|
spin_unlock_irqrestore(&pp->conf_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int pcie_wr_conf(struct pci_bus *bus, u32 devfn,
|
|
int where, int size, u32 val)
|
|
{
|
|
struct pci_sys_data *sys = bus->sysdata;
|
|
struct pcie_port *pp = sys->private_data;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
if (pcie_valid_config(pp, bus->number, PCI_SLOT(devfn)) == 0)
|
|
return PCIBIOS_DEVICE_NOT_FOUND;
|
|
|
|
spin_lock_irqsave(&pp->conf_lock, flags);
|
|
ret = orion_pcie_wr_conf(pp->base, bus, devfn, where, size, val);
|
|
spin_unlock_irqrestore(&pp->conf_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct pci_ops pcie_ops = {
|
|
.read = pcie_rd_conf,
|
|
.write = pcie_wr_conf,
|
|
};
|
|
|
|
static void __init pcie0_ioresources_init(struct pcie_port *pp)
|
|
{
|
|
pp->base = (void __iomem *)PCIE_VIRT_BASE;
|
|
pp->irq = IRQ_KIRKWOOD_PCIE;
|
|
|
|
/*
|
|
* IORESOURCE_IO
|
|
*/
|
|
pp->res[0].name = "PCIe 0 I/O Space";
|
|
pp->res[0].start = KIRKWOOD_PCIE_IO_BUS_BASE;
|
|
pp->res[0].end = pp->res[0].start + KIRKWOOD_PCIE_IO_SIZE - 1;
|
|
pp->res[0].flags = IORESOURCE_IO;
|
|
|
|
/*
|
|
* IORESOURCE_MEM
|
|
*/
|
|
pp->res[1].name = "PCIe 0 MEM";
|
|
pp->res[1].start = KIRKWOOD_PCIE_MEM_PHYS_BASE;
|
|
pp->res[1].end = pp->res[1].start + KIRKWOOD_PCIE_MEM_SIZE - 1;
|
|
pp->res[1].flags = IORESOURCE_MEM;
|
|
}
|
|
|
|
static void __init pcie1_ioresources_init(struct pcie_port *pp)
|
|
{
|
|
pp->base = (void __iomem *)PCIE1_VIRT_BASE;
|
|
pp->irq = IRQ_KIRKWOOD_PCIE1;
|
|
|
|
/*
|
|
* IORESOURCE_IO
|
|
*/
|
|
pp->res[0].name = "PCIe 1 I/O Space";
|
|
pp->res[0].start = KIRKWOOD_PCIE1_IO_BUS_BASE;
|
|
pp->res[0].end = pp->res[0].start + KIRKWOOD_PCIE1_IO_SIZE - 1;
|
|
pp->res[0].flags = IORESOURCE_IO;
|
|
|
|
/*
|
|
* IORESOURCE_MEM
|
|
*/
|
|
pp->res[1].name = "PCIe 1 MEM";
|
|
pp->res[1].start = KIRKWOOD_PCIE1_MEM_PHYS_BASE;
|
|
pp->res[1].end = pp->res[1].start + KIRKWOOD_PCIE1_MEM_SIZE - 1;
|
|
pp->res[1].flags = IORESOURCE_MEM;
|
|
}
|
|
|
|
static int __init kirkwood_pcie_setup(int nr, struct pci_sys_data *sys)
|
|
{
|
|
struct pcie_port *pp;
|
|
int index;
|
|
|
|
if (nr >= num_pcie_ports)
|
|
return 0;
|
|
|
|
index = pcie_port_map[nr];
|
|
printk(KERN_INFO "PCI: bus%d uses PCIe port %d\n", sys->busnr, index);
|
|
|
|
pp = kzalloc(sizeof(*pp), GFP_KERNEL);
|
|
if (!pp)
|
|
panic("PCIe: failed to allocate pcie_port data");
|
|
sys->private_data = pp;
|
|
pp->root_bus_nr = sys->busnr;
|
|
spin_lock_init(&pp->conf_lock);
|
|
|
|
switch (index) {
|
|
case 0:
|
|
kirkwood_enable_pcie_clk("0");
|
|
pcie0_ioresources_init(pp);
|
|
break;
|
|
case 1:
|
|
kirkwood_enable_pcie_clk("1");
|
|
pcie1_ioresources_init(pp);
|
|
break;
|
|
default:
|
|
panic("PCIe setup: invalid controller %d", index);
|
|
}
|
|
|
|
if (request_resource(&ioport_resource, &pp->res[0]))
|
|
panic("Request PCIe%d IO resource failed\n", index);
|
|
if (request_resource(&iomem_resource, &pp->res[1]))
|
|
panic("Request PCIe%d Memory resource failed\n", index);
|
|
|
|
sys->io_offset = 0;
|
|
pci_add_resource_offset(&sys->resources, &pp->res[0], sys->io_offset);
|
|
pci_add_resource_offset(&sys->resources, &pp->res[1], sys->mem_offset);
|
|
|
|
/*
|
|
* Generic PCIe unit setup.
|
|
*/
|
|
orion_pcie_set_local_bus_nr(pp->base, sys->busnr);
|
|
|
|
orion_pcie_setup(pp->base);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void __devinit rc_pci_fixup(struct pci_dev *dev)
|
|
{
|
|
/*
|
|
* Prevent enumeration of root complex.
|
|
*/
|
|
if (dev->bus->parent == NULL && dev->devfn == 0) {
|
|
int i;
|
|
|
|
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
|
|
dev->resource[i].start = 0;
|
|
dev->resource[i].end = 0;
|
|
dev->resource[i].flags = 0;
|
|
}
|
|
}
|
|
}
|
|
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MARVELL, PCI_ANY_ID, rc_pci_fixup);
|
|
|
|
static struct pci_bus __init *
|
|
kirkwood_pcie_scan_bus(int nr, struct pci_sys_data *sys)
|
|
{
|
|
struct pci_bus *bus;
|
|
|
|
if (nr < num_pcie_ports) {
|
|
bus = pci_scan_root_bus(NULL, sys->busnr, &pcie_ops, sys,
|
|
&sys->resources);
|
|
} else {
|
|
bus = NULL;
|
|
BUG();
|
|
}
|
|
|
|
return bus;
|
|
}
|
|
|
|
static int __init kirkwood_pcie_map_irq(const struct pci_dev *dev, u8 slot,
|
|
u8 pin)
|
|
{
|
|
struct pci_sys_data *sys = dev->sysdata;
|
|
struct pcie_port *pp = sys->private_data;
|
|
|
|
return pp->irq;
|
|
}
|
|
|
|
static struct hw_pci kirkwood_pci __initdata = {
|
|
.setup = kirkwood_pcie_setup,
|
|
.scan = kirkwood_pcie_scan_bus,
|
|
.map_irq = kirkwood_pcie_map_irq,
|
|
};
|
|
|
|
static void __init add_pcie_port(int index, unsigned long base)
|
|
{
|
|
printk(KERN_INFO "Kirkwood PCIe port %d: ", index);
|
|
|
|
if (orion_pcie_link_up((void __iomem *)base)) {
|
|
printk(KERN_INFO "link up\n");
|
|
pcie_port_map[num_pcie_ports++] = index;
|
|
} else
|
|
printk(KERN_INFO "link down, ignoring\n");
|
|
}
|
|
|
|
void __init kirkwood_pcie_init(unsigned int portmask)
|
|
{
|
|
vga_base = KIRKWOOD_PCIE_MEM_PHYS_BASE;
|
|
|
|
if (portmask & KW_PCIE0)
|
|
add_pcie_port(0, PCIE_VIRT_BASE);
|
|
|
|
if (portmask & KW_PCIE1)
|
|
add_pcie_port(1, PCIE1_VIRT_BASE);
|
|
|
|
kirkwood_pci.nr_controllers = num_pcie_ports;
|
|
pci_common_init(&kirkwood_pci);
|
|
}
|