| /* |
| * Copyright 2003 PMC-Sierra |
| * Author: Manish Lachwani (lachwani@pmc-sierra.com) |
| * |
| * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN |
| * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * 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., |
| * 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| #include <linux/types.h> |
| #include <linux/pci.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <asm/pci.h> |
| #include <asm/io.h> |
| |
| #include <linux/init.h> |
| #include <asm/titan_dep.h> |
| |
| #ifdef CONFIG_HYPERTRANSPORT |
| |
| |
| /* |
| * This function check if the Hypertransport Link Initialization completed. If |
| * it did, then proceed further with scanning bus #2 |
| */ |
| static __inline__ int check_titan_htlink(void) |
| { |
| u32 val; |
| |
| val = *(volatile uint32_t *)(RM9000x2_HTLINK_REG); |
| if (val & 0x00000020) |
| /* HT Link Initialization completed */ |
| return 1; |
| else |
| return 0; |
| } |
| |
| static int titan_ht_config_read_dword(struct pci_dev *device, |
| int offset, u32* val) |
| { |
| int dev, bus, func; |
| uint32_t address_reg, data_reg; |
| uint32_t address; |
| |
| bus = device->bus->number; |
| dev = PCI_SLOT(device->devfn); |
| func = PCI_FUNC(device->devfn); |
| |
| /* XXX Need to change the Bus # */ |
| if (bus > 2) |
| address = (bus << 16) | (dev << 11) | (func << 8) | (offset & 0xfc) | |
| 0x80000000 | 0x1; |
| else |
| address = (dev << 11) | (func << 8) | (offset & 0xfc) | 0x80000000; |
| |
| address_reg = RM9000x2_OCD_HTCFGA; |
| data_reg = RM9000x2_OCD_HTCFGD; |
| |
| RM9K_WRITE(address_reg, address); |
| RM9K_READ(data_reg, val); |
| |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| |
| static int titan_ht_config_read_word(struct pci_dev *device, |
| int offset, u16* val) |
| { |
| int dev, bus, func; |
| uint32_t address_reg, data_reg; |
| uint32_t address; |
| |
| bus = device->bus->number; |
| dev = PCI_SLOT(device->devfn); |
| func = PCI_FUNC(device->devfn); |
| |
| /* XXX Need to change the Bus # */ |
| if (bus > 2) |
| address = (bus << 16) | (dev << 11) | (func << 8) | (offset & 0xfc) | |
| 0x80000000 | 0x1; |
| else |
| address = (dev << 11) | (func << 8) | (offset & 0xfc) | 0x80000000; |
| |
| address_reg = RM9000x2_OCD_HTCFGA; |
| data_reg = RM9000x2_OCD_HTCFGD; |
| |
| if ((offset & 0x3) == 0) |
| offset = 0x2; |
| else |
| offset = 0x0; |
| |
| RM9K_WRITE(address_reg, address); |
| RM9K_READ_16(data_reg + offset, val); |
| |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| |
| u32 longswap(unsigned long l) |
| { |
| unsigned char b1,b2,b3,b4; |
| |
| b1 = l&255; |
| b2 = (l>>8)&255; |
| b3 = (l>>16)&255; |
| b4 = (l>>24)&255; |
| |
| return ((b1<<24) + (b2<<16) + (b3<<8) + b4); |
| } |
| |
| |
| static int titan_ht_config_read_byte(struct pci_dev *device, |
| int offset, u8* val) |
| { |
| int dev, bus, func; |
| uint32_t address_reg, data_reg; |
| uint32_t address; |
| int offset1; |
| |
| bus = device->bus->number; |
| dev = PCI_SLOT(device->devfn); |
| func = PCI_FUNC(device->devfn); |
| |
| /* XXX Need to change the Bus # */ |
| if (bus > 2) |
| address = (bus << 16) | (dev << 11) | (func << 8) | (offset & 0xfc) | |
| 0x80000000 | 0x1; |
| else |
| address = (dev << 11) | (func << 8) | (offset & 0xfc) | 0x80000000; |
| |
| address_reg = RM9000x2_OCD_HTCFGA; |
| data_reg = RM9000x2_OCD_HTCFGD; |
| |
| RM9K_WRITE(address_reg, address); |
| |
| if ((offset & 0x3) == 0) { |
| offset1 = 0x3; |
| } |
| if ((offset & 0x3) == 1) { |
| offset1 = 0x2; |
| } |
| if ((offset & 0x3) == 2) { |
| offset1 = 0x1; |
| } |
| if ((offset & 0x3) == 3) { |
| offset1 = 0x0; |
| } |
| RM9K_READ_8(data_reg + offset1, val); |
| |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| |
| static int titan_ht_config_write_dword(struct pci_dev *device, |
| int offset, u8 val) |
| { |
| int dev, bus, func; |
| uint32_t address_reg, data_reg; |
| uint32_t address; |
| |
| bus = device->bus->number; |
| dev = PCI_SLOT(device->devfn); |
| func = PCI_FUNC(device->devfn); |
| |
| /* XXX Need to change the Bus # */ |
| if (bus > 2) |
| address = (bus << 16) | (dev << 11) | (func << 8) | (offset & 0xfc) | |
| 0x80000000 | 0x1; |
| else |
| address = (dev << 11) | (func << 8) | (offset & 0xfc) | 0x80000000; |
| |
| address_reg = RM9000x2_OCD_HTCFGA; |
| data_reg = RM9000x2_OCD_HTCFGD; |
| |
| RM9K_WRITE(address_reg, address); |
| RM9K_WRITE(data_reg, val); |
| |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| static int titan_ht_config_write_word(struct pci_dev *device, |
| int offset, u8 val) |
| { |
| int dev, bus, func; |
| uint32_t address_reg, data_reg; |
| uint32_t address; |
| |
| bus = device->bus->number; |
| dev = PCI_SLOT(device->devfn); |
| func = PCI_FUNC(device->devfn); |
| |
| /* XXX Need to change the Bus # */ |
| if (bus > 2) |
| address = (bus << 16) | (dev << 11) | (func << 8) | (offset & 0xfc) | |
| 0x80000000 | 0x1; |
| else |
| address = (dev << 11) | (func << 8) | (offset & 0xfc) | 0x80000000; |
| |
| address_reg = RM9000x2_OCD_HTCFGA; |
| data_reg = RM9000x2_OCD_HTCFGD; |
| |
| if ((offset & 0x3) == 0) |
| offset = 0x2; |
| else |
| offset = 0x0; |
| |
| RM9K_WRITE(address_reg, address); |
| RM9K_WRITE_16(data_reg + offset, val); |
| |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| static int titan_ht_config_write_byte(struct pci_dev *device, |
| int offset, u8 val) |
| { |
| int dev, bus, func; |
| uint32_t address_reg, data_reg; |
| uint32_t address; |
| int offset1; |
| |
| bus = device->bus->number; |
| dev = PCI_SLOT(device->devfn); |
| func = PCI_FUNC(device->devfn); |
| |
| /* XXX Need to change the Bus # */ |
| if (bus > 2) |
| address = (bus << 16) | (dev << 11) | (func << 8) | (offset & 0xfc) | |
| 0x80000000 | 0x1; |
| else |
| address = (dev << 11) | (func << 8) | (offset & 0xfc) | 0x80000000; |
| |
| address_reg = RM9000x2_OCD_HTCFGA; |
| data_reg = RM9000x2_OCD_HTCFGD; |
| |
| RM9K_WRITE(address_reg, address); |
| |
| if ((offset & 0x3) == 0) { |
| offset1 = 0x3; |
| } |
| if ((offset & 0x3) == 1) { |
| offset1 = 0x2; |
| } |
| if ((offset & 0x3) == 2) { |
| offset1 = 0x1; |
| } |
| if ((offset & 0x3) == 3) { |
| offset1 = 0x0; |
| } |
| |
| RM9K_WRITE_8(data_reg + offset1, val); |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| |
| static void titan_pcibios_set_master(struct pci_dev *dev) |
| { |
| u16 cmd; |
| int bus = dev->bus->number; |
| |
| if (check_titan_htlink()) |
| titan_ht_config_read_word(dev, PCI_COMMAND, &cmd); |
| |
| cmd |= PCI_COMMAND_MASTER; |
| |
| if (check_titan_htlink()) |
| titan_ht_config_write_word(dev, PCI_COMMAND, cmd); |
| } |
| |
| |
| int pcibios_enable_resources(struct pci_dev *dev) |
| { |
| u16 cmd, old_cmd; |
| u8 tmp1; |
| int idx; |
| struct resource *r; |
| int bus = dev->bus->number; |
| |
| if (check_titan_htlink()) |
| titan_ht_config_read_word(dev, PCI_COMMAND, &cmd); |
| |
| old_cmd = cmd; |
| for (idx = 0; idx < 6; idx++) { |
| r = &dev->resource[idx]; |
| if (!r->start && r->end) { |
| printk(KERN_ERR |
| "PCI: Device %s not available because of " |
| "resource collisions\n", pci_name(dev)); |
| return -EINVAL; |
| } |
| if (r->flags & IORESOURCE_IO) |
| cmd |= PCI_COMMAND_IO; |
| if (r->flags & IORESOURCE_MEM) |
| cmd |= PCI_COMMAND_MEMORY; |
| } |
| if (cmd != old_cmd) { |
| if (check_titan_htlink()) |
| titan_ht_config_write_word(dev, PCI_COMMAND, cmd); |
| } |
| |
| if (check_titan_htlink()) |
| titan_ht_config_read_byte(dev, PCI_CACHE_LINE_SIZE, &tmp1); |
| |
| if (tmp1 != 8) { |
| printk(KERN_WARNING "PCI setting cache line size to 8 from " |
| "%d\n", tmp1); |
| } |
| |
| if (check_titan_htlink()) |
| titan_ht_config_write_byte(dev, PCI_CACHE_LINE_SIZE, 8); |
| |
| if (check_titan_htlink()) |
| titan_ht_config_read_byte(dev, PCI_LATENCY_TIMER, &tmp1); |
| |
| if (tmp1 < 32 || tmp1 == 0xff) { |
| printk(KERN_WARNING "PCI setting latency timer to 32 from %d\n", |
| tmp1); |
| } |
| |
| if (check_titan_htlink()) |
| titan_ht_config_write_byte(dev, PCI_LATENCY_TIMER, 32); |
| |
| return 0; |
| } |
| |
| |
| int pcibios_enable_device(struct pci_dev *dev, int mask) |
| { |
| return pcibios_enable_resources(dev); |
| } |
| |
| |
| |
| void pcibios_update_resource(struct pci_dev *dev, struct resource *root, |
| struct resource *res, int resource) |
| { |
| u32 new, check; |
| int reg; |
| |
| return; |
| |
| new = res->start | (res->flags & PCI_REGION_FLAG_MASK); |
| if (resource < 6) { |
| reg = PCI_BASE_ADDRESS_0 + 4 * resource; |
| } else if (resource == PCI_ROM_RESOURCE) { |
| res->flags |= IORESOURCE_ROM_ENABLE; |
| reg = dev->rom_base_reg; |
| } else { |
| /* |
| * Somebody might have asked allocation of a non-standard |
| * resource |
| */ |
| return; |
| } |
| |
| pci_write_config_dword(dev, reg, new); |
| pci_read_config_dword(dev, reg, &check); |
| if ((new ^ check) & |
| ((new & PCI_BASE_ADDRESS_SPACE_IO) ? PCI_BASE_ADDRESS_IO_MASK : |
| PCI_BASE_ADDRESS_MEM_MASK)) { |
| printk(KERN_ERR "PCI: Error while updating region " |
| "%s/%d (%08x != %08x)\n", pci_name(dev), resource, |
| new, check); |
| } |
| } |
| |
| |
| void pcibios_align_resource(void *data, struct resource *res, |
| resource_size_t size, resource_size_t align) |
| { |
| struct pci_dev *dev = data; |
| |
| if (res->flags & IORESOURCE_IO) { |
| resource_size_t start = res->start; |
| |
| /* We need to avoid collisions with `mirrored' VGA ports |
| and other strange ISA hardware, so we always want the |
| addresses kilobyte aligned. */ |
| if (size > 0x100) { |
| printk(KERN_ERR "PCI: I/O Region %s/%d too large" |
| " (%ld bytes)\n", pci_name(dev), |
| dev->resource - res, size); |
| } |
| |
| start = (start + 1024 - 1) & ~(1024 - 1); |
| res->start = start; |
| } |
| } |
| |
| struct pci_ops titan_pci_ops = { |
| titan_ht_config_read_byte, |
| titan_ht_config_read_word, |
| titan_ht_config_read_dword, |
| titan_ht_config_write_byte, |
| titan_ht_config_write_word, |
| titan_ht_config_write_dword |
| }; |
| |
| void __init pcibios_fixup_bus(struct pci_bus *c) |
| { |
| titan_ht_pcibios_fixup_bus(c); |
| } |
| |
| void __init pcibios_init(void) |
| { |
| |
| /* Reset PCI I/O and PCI MEM values */ |
| /* XXX Need to add the proper values here */ |
| ioport_resource.start = 0xe0000000; |
| ioport_resource.end = 0xe0000000 + 0x20000000 - 1; |
| iomem_resource.start = 0xc0000000; |
| iomem_resource.end = 0xc0000000 + 0x20000000 - 1; |
| |
| /* XXX Need to add bus values */ |
| pci_scan_bus(2, &titan_pci_ops, NULL); |
| pci_scan_bus(3, &titan_pci_ops, NULL); |
| } |
| |
| /* |
| * for parsing "pci=" kernel boot arguments. |
| */ |
| char *pcibios_setup(char *str) |
| { |
| printk(KERN_INFO "rr: pcibios_setup\n"); |
| /* Nothing to do for now. */ |
| |
| return str; |
| } |
| |
| unsigned __init int pcibios_assign_all_busses(void) |
| { |
| /* We want to use the PCI bus detection done by PMON */ |
| return 0; |
| } |
| |
| #endif /* CONFIG_HYPERTRANSPORT */ |