|  | /* $Id: sbus.c,v 1.100 2002/01/24 15:36:24 davem Exp $ | 
|  | * sbus.c:  SBus support routines. | 
|  | * | 
|  | * Copyright (C) 1995 David S. Miller ([email protected]) | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/config.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/pci.h> | 
|  |  | 
|  | #include <asm/system.h> | 
|  | #include <asm/sbus.h> | 
|  | #include <asm/dma.h> | 
|  | #include <asm/oplib.h> | 
|  | #include <asm/bpp.h> | 
|  | #include <asm/irq.h> | 
|  |  | 
|  | struct sbus_bus *sbus_root = NULL; | 
|  |  | 
|  | static struct linux_prom_irqs irqs[PROMINTR_MAX] __initdata = { { 0 } }; | 
|  | #ifdef CONFIG_SPARC32 | 
|  | static int interrupts[PROMINTR_MAX] __initdata = { 0 }; | 
|  | #endif | 
|  |  | 
|  | #ifdef CONFIG_PCI | 
|  | extern int pcic_present(void); | 
|  | #endif | 
|  |  | 
|  | /* Perhaps when I figure out more about the iommu we'll put a | 
|  | * device registration routine here that probe_sbus() calls to | 
|  | * setup the iommu for each Sbus. | 
|  | */ | 
|  |  | 
|  | /* We call this for each SBus device, and fill the structure based | 
|  | * upon the prom device tree.  We return the start of memory after | 
|  | * the things we have allocated. | 
|  | */ | 
|  |  | 
|  | /* #define DEBUG_FILL */ | 
|  |  | 
|  | static void __init fill_sbus_device(int prom_node, struct sbus_dev *sdev) | 
|  | { | 
|  | unsigned long address, base; | 
|  | int len; | 
|  |  | 
|  | sdev->prom_node = prom_node; | 
|  | prom_getstring(prom_node, "name", | 
|  | sdev->prom_name, sizeof(sdev->prom_name)); | 
|  | address = prom_getint(prom_node, "address"); | 
|  | len = prom_getproperty(prom_node, "reg", | 
|  | (char *) sdev->reg_addrs, | 
|  | sizeof(sdev->reg_addrs)); | 
|  | if (len == -1) { | 
|  | sdev->num_registers = 0; | 
|  | goto no_regs; | 
|  | } | 
|  |  | 
|  | if (len % sizeof(struct linux_prom_registers)) { | 
|  | prom_printf("fill_sbus_device: proplen for regs of %s " | 
|  | " was %d, need multiple of %d\n", | 
|  | sdev->prom_name, len, | 
|  | (int) sizeof(struct linux_prom_registers)); | 
|  | prom_halt(); | 
|  | } | 
|  | if (len > (sizeof(struct linux_prom_registers) * PROMREG_MAX)) { | 
|  | prom_printf("fill_sbus_device: Too many register properties " | 
|  | "for device %s, len=%d\n", | 
|  | sdev->prom_name, len); | 
|  | prom_halt(); | 
|  | } | 
|  | sdev->num_registers = len / sizeof(struct linux_prom_registers); | 
|  | sdev->ranges_applied = 0; | 
|  |  | 
|  | base = (unsigned long) sdev->reg_addrs[0].phys_addr; | 
|  |  | 
|  | /* Compute the slot number. */ | 
|  | if (base >= SUN_SBUS_BVADDR && sparc_cpu_model == sun4m) { | 
|  | sdev->slot = sbus_dev_slot(base); | 
|  | } else { | 
|  | sdev->slot = sdev->reg_addrs[0].which_io; | 
|  | } | 
|  |  | 
|  | no_regs: | 
|  | len = prom_getproperty(prom_node, "ranges", | 
|  | (char *)sdev->device_ranges, | 
|  | sizeof(sdev->device_ranges)); | 
|  | if (len == -1) { | 
|  | sdev->num_device_ranges = 0; | 
|  | goto no_ranges; | 
|  | } | 
|  | if (len % sizeof(struct linux_prom_ranges)) { | 
|  | prom_printf("fill_sbus_device: proplen for ranges of %s " | 
|  | " was %d, need multiple of %d\n", | 
|  | sdev->prom_name, len, | 
|  | (int) sizeof(struct linux_prom_ranges)); | 
|  | prom_halt(); | 
|  | } | 
|  | if (len > (sizeof(struct linux_prom_ranges) * PROMREG_MAX)) { | 
|  | prom_printf("fill_sbus_device: Too many range properties " | 
|  | "for device %s, len=%d\n", | 
|  | sdev->prom_name, len); | 
|  | prom_halt(); | 
|  | } | 
|  | sdev->num_device_ranges = | 
|  | len / sizeof(struct linux_prom_ranges); | 
|  |  | 
|  | no_ranges: | 
|  | /* XXX Unfortunately, IRQ issues are very arch specific. | 
|  | * XXX Pull this crud out into an arch specific area | 
|  | * XXX at some point. -DaveM | 
|  | */ | 
|  | #ifdef CONFIG_SPARC64 | 
|  | len = prom_getproperty(prom_node, "interrupts", | 
|  | (char *) irqs, sizeof(irqs)); | 
|  | if (len == -1 || len == 0) { | 
|  | sdev->irqs[0] = 0; | 
|  | sdev->num_irqs = 0; | 
|  | } else { | 
|  | unsigned int pri = irqs[0].pri; | 
|  |  | 
|  | sdev->num_irqs = 1; | 
|  | if (pri < 0x20) | 
|  | pri += sdev->slot * 8; | 
|  |  | 
|  | sdev->irqs[0] =	sbus_build_irq(sdev->bus, pri); | 
|  | } | 
|  | #endif /* CONFIG_SPARC64 */ | 
|  |  | 
|  | #ifdef CONFIG_SPARC32 | 
|  | len = prom_getproperty(prom_node, "intr", | 
|  | (char *)irqs, sizeof(irqs)); | 
|  | if (len != -1) { | 
|  | sdev->num_irqs = len / 8; | 
|  | if (sdev->num_irqs == 0) { | 
|  | sdev->irqs[0] = 0; | 
|  | } else if (sparc_cpu_model == sun4d) { | 
|  | extern unsigned int sun4d_build_irq(struct sbus_dev *sdev, int irq); | 
|  |  | 
|  | for (len = 0; len < sdev->num_irqs; len++) | 
|  | sdev->irqs[len] = sun4d_build_irq(sdev, irqs[len].pri); | 
|  | } else { | 
|  | for (len = 0; len < sdev->num_irqs; len++) | 
|  | sdev->irqs[len] = irqs[len].pri; | 
|  | } | 
|  | } else { | 
|  | /* No "intr" node found-- check for "interrupts" node. | 
|  | * This node contains SBus interrupt levels, not IPLs | 
|  | * as in "intr", and no vector values.  We convert | 
|  | * SBus interrupt levels to PILs (platform specific). | 
|  | */ | 
|  | len = prom_getproperty(prom_node, "interrupts", | 
|  | (char *)interrupts, sizeof(interrupts)); | 
|  | if (len == -1) { | 
|  | sdev->irqs[0] = 0; | 
|  | sdev->num_irqs = 0; | 
|  | } else { | 
|  | sdev->num_irqs = len / sizeof(int); | 
|  | for (len = 0; len < sdev->num_irqs; len++) { | 
|  | sdev->irqs[len] = sbint_to_irq(sdev, interrupts[len]); | 
|  | } | 
|  | } | 
|  | } | 
|  | #endif /* CONFIG_SPARC32 */ | 
|  | } | 
|  |  | 
|  | /* This routine gets called from whoever needs the sbus first, to scan | 
|  | * the SBus device tree.  Currently it just prints out the devices | 
|  | * found on the bus and builds trees of SBUS structs and attached | 
|  | * devices. | 
|  | */ | 
|  |  | 
|  | extern void iommu_init(int iommu_node, struct sbus_bus *sbus); | 
|  | extern void iounit_init(int sbi_node, int iounit_node, struct sbus_bus *sbus); | 
|  | void sun4_init(void); | 
|  | #ifdef CONFIG_SUN_AUXIO | 
|  | extern void auxio_probe(void); | 
|  | #endif | 
|  |  | 
|  | static void __init sbus_do_child_siblings(int start_node, | 
|  | struct sbus_dev *child, | 
|  | struct sbus_dev *parent, | 
|  | struct sbus_bus *sbus) | 
|  | { | 
|  | struct sbus_dev *this_dev = child; | 
|  | int this_node = start_node; | 
|  |  | 
|  | /* Child already filled in, just need to traverse siblings. */ | 
|  | child->child = NULL; | 
|  | child->parent = parent; | 
|  | while((this_node = prom_getsibling(this_node)) != 0) { | 
|  | this_dev->next = kmalloc(sizeof(struct sbus_dev), GFP_ATOMIC); | 
|  | this_dev = this_dev->next; | 
|  | this_dev->next = NULL; | 
|  | this_dev->parent = parent; | 
|  |  | 
|  | this_dev->bus = sbus; | 
|  | fill_sbus_device(this_node, this_dev); | 
|  |  | 
|  | if(prom_getchild(this_node)) { | 
|  | this_dev->child = kmalloc(sizeof(struct sbus_dev), | 
|  | GFP_ATOMIC); | 
|  | this_dev->child->bus = sbus; | 
|  | this_dev->child->next = NULL; | 
|  | fill_sbus_device(prom_getchild(this_node), this_dev->child); | 
|  | sbus_do_child_siblings(prom_getchild(this_node), | 
|  | this_dev->child, this_dev, sbus); | 
|  | } else { | 
|  | this_dev->child = NULL; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * XXX This functions appears to be a distorted version of | 
|  | * prom_sbus_ranges_init(), with all sun4d stuff cut away. | 
|  | * Ask DaveM what is going on here, how is sun4d supposed to work... XXX | 
|  | */ | 
|  | /* added back sun4d patch from Thomas Bogendoerfer - should be OK (crn) */ | 
|  |  | 
|  | static void __init sbus_bus_ranges_init(int parent_node, struct sbus_bus *sbus) | 
|  | { | 
|  | int len; | 
|  |  | 
|  | len = prom_getproperty(sbus->prom_node, "ranges", | 
|  | (char *) sbus->sbus_ranges, | 
|  | sizeof(sbus->sbus_ranges)); | 
|  | if (len == -1 || len == 0) { | 
|  | sbus->num_sbus_ranges = 0; | 
|  | return; | 
|  | } | 
|  | sbus->num_sbus_ranges = len / sizeof(struct linux_prom_ranges); | 
|  | #ifdef CONFIG_SPARC32 | 
|  | if (sparc_cpu_model == sun4d) { | 
|  | struct linux_prom_ranges iounit_ranges[PROMREG_MAX]; | 
|  | int num_iounit_ranges; | 
|  |  | 
|  | len = prom_getproperty(parent_node, "ranges", | 
|  | (char *) iounit_ranges, | 
|  | sizeof (iounit_ranges)); | 
|  | if (len != -1) { | 
|  | num_iounit_ranges = (len/sizeof(struct linux_prom_ranges)); | 
|  | prom_adjust_ranges (sbus->sbus_ranges, sbus->num_sbus_ranges, iounit_ranges, num_iounit_ranges); | 
|  | } | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static void __init __apply_ranges_to_regs(struct linux_prom_ranges *ranges, | 
|  | int num_ranges, | 
|  | struct linux_prom_registers *regs, | 
|  | int num_regs) | 
|  | { | 
|  | if (num_ranges) { | 
|  | int regnum; | 
|  |  | 
|  | for (regnum = 0; regnum < num_regs; regnum++) { | 
|  | int rngnum; | 
|  |  | 
|  | for (rngnum = 0; rngnum < num_ranges; rngnum++) { | 
|  | if (regs[regnum].which_io == ranges[rngnum].ot_child_space) | 
|  | break; | 
|  | } | 
|  | if (rngnum == num_ranges) { | 
|  | /* We used to flag this as an error.  Actually | 
|  | * some devices do not report the regs as we expect. | 
|  | * For example, see SUNW,pln device.  In that case | 
|  | * the reg property is in a format internal to that | 
|  | * node, ie. it is not in the SBUS register space | 
|  | * per se. -DaveM | 
|  | */ | 
|  | return; | 
|  | } | 
|  | regs[regnum].which_io = ranges[rngnum].ot_parent_space; | 
|  | regs[regnum].phys_addr -= ranges[rngnum].ot_child_base; | 
|  | regs[regnum].phys_addr += ranges[rngnum].ot_parent_base; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void __init __fixup_regs_sdev(struct sbus_dev *sdev) | 
|  | { | 
|  | if (sdev->num_registers != 0) { | 
|  | struct sbus_dev *parent = sdev->parent; | 
|  | int i; | 
|  |  | 
|  | while (parent != NULL) { | 
|  | __apply_ranges_to_regs(parent->device_ranges, | 
|  | parent->num_device_ranges, | 
|  | sdev->reg_addrs, | 
|  | sdev->num_registers); | 
|  |  | 
|  | parent = parent->parent; | 
|  | } | 
|  |  | 
|  | __apply_ranges_to_regs(sdev->bus->sbus_ranges, | 
|  | sdev->bus->num_sbus_ranges, | 
|  | sdev->reg_addrs, | 
|  | sdev->num_registers); | 
|  |  | 
|  | for (i = 0; i < sdev->num_registers; i++) { | 
|  | struct resource *res = &sdev->resource[i]; | 
|  |  | 
|  | res->start = sdev->reg_addrs[i].phys_addr; | 
|  | res->end = (res->start + | 
|  | (unsigned long)sdev->reg_addrs[i].reg_size - 1UL); | 
|  | res->flags = IORESOURCE_IO | | 
|  | (sdev->reg_addrs[i].which_io & 0xff); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void __init sbus_fixup_all_regs(struct sbus_dev *first_sdev) | 
|  | { | 
|  | struct sbus_dev *sdev; | 
|  |  | 
|  | for (sdev = first_sdev; sdev; sdev = sdev->next) { | 
|  | if (sdev->child) | 
|  | sbus_fixup_all_regs(sdev->child); | 
|  | __fixup_regs_sdev(sdev); | 
|  | } | 
|  | } | 
|  |  | 
|  | extern void register_proc_sparc_ioport(void); | 
|  | extern void firetruck_init(void); | 
|  |  | 
|  | #ifdef CONFIG_SUN4 | 
|  | extern void sun4_dvma_init(void); | 
|  | #endif | 
|  |  | 
|  | static int __init sbus_init(void) | 
|  | { | 
|  | int nd, this_sbus, sbus_devs, topnd, iommund; | 
|  | unsigned int sbus_clock; | 
|  | struct sbus_bus *sbus; | 
|  | struct sbus_dev *this_dev; | 
|  | int num_sbus = 0;  /* How many did we find? */ | 
|  |  | 
|  | #ifdef CONFIG_SPARC32 | 
|  | register_proc_sparc_ioport(); | 
|  | #endif | 
|  |  | 
|  | #ifdef CONFIG_SUN4 | 
|  | sun4_dvma_init(); | 
|  | return 0; | 
|  | #endif | 
|  |  | 
|  | topnd = prom_getchild(prom_root_node); | 
|  |  | 
|  | /* Finding the first sbus is a special case... */ | 
|  | iommund = 0; | 
|  | if(sparc_cpu_model == sun4u) { | 
|  | nd = prom_searchsiblings(topnd, "sbus"); | 
|  | if(nd == 0) { | 
|  | #ifdef CONFIG_PCI | 
|  | if (!pcic_present()) { | 
|  | prom_printf("Neither SBUS nor PCI found.\n"); | 
|  | prom_halt(); | 
|  | } else { | 
|  | #ifdef CONFIG_SPARC64 | 
|  | firetruck_init(); | 
|  | #endif | 
|  | } | 
|  | return 0; | 
|  | #else | 
|  | prom_printf("YEEE, UltraSparc sbus not found\n"); | 
|  | prom_halt(); | 
|  | #endif | 
|  | } | 
|  | } else if(sparc_cpu_model == sun4d) { | 
|  | if((iommund = prom_searchsiblings(topnd, "io-unit")) == 0 || | 
|  | (nd = prom_getchild(iommund)) == 0 || | 
|  | (nd = prom_searchsiblings(nd, "sbi")) == 0) { | 
|  | panic("sbi not found"); | 
|  | } | 
|  | } else if((nd = prom_searchsiblings(topnd, "sbus")) == 0) { | 
|  | if((iommund = prom_searchsiblings(topnd, "iommu")) == 0 || | 
|  | (nd = prom_getchild(iommund)) == 0 || | 
|  | (nd = prom_searchsiblings(nd, "sbus")) == 0) { | 
|  | #ifdef CONFIG_PCI | 
|  | if (!pcic_present()) { | 
|  | prom_printf("Neither SBUS nor PCI found.\n"); | 
|  | prom_halt(); | 
|  | } | 
|  | return 0; | 
|  | #else | 
|  | /* No reason to run further - the data access trap will occur. */ | 
|  | panic("sbus not found"); | 
|  | #endif | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Ok, we've found the first one, allocate first SBus struct | 
|  | * and place in chain. | 
|  | */ | 
|  | sbus = sbus_root = kmalloc(sizeof(struct sbus_bus), GFP_ATOMIC); | 
|  | sbus->next = NULL; | 
|  | sbus->prom_node = nd; | 
|  | this_sbus = nd; | 
|  |  | 
|  | if(iommund && sparc_cpu_model != sun4u && sparc_cpu_model != sun4d) | 
|  | iommu_init(iommund, sbus); | 
|  |  | 
|  | /* Loop until we find no more SBUS's */ | 
|  | while(this_sbus) { | 
|  | #ifdef CONFIG_SPARC64 | 
|  | /* IOMMU hides inside SBUS/SYSIO prom node on Ultra. */ | 
|  | if(sparc_cpu_model == sun4u) { | 
|  | extern void sbus_iommu_init(int prom_node, struct sbus_bus *sbus); | 
|  |  | 
|  | sbus_iommu_init(this_sbus, sbus); | 
|  | } | 
|  | #endif /* CONFIG_SPARC64 */ | 
|  |  | 
|  | #ifdef CONFIG_SPARC32 | 
|  | if (sparc_cpu_model == sun4d) | 
|  | iounit_init(this_sbus, iommund, sbus); | 
|  | #endif /* CONFIG_SPARC32 */ | 
|  | printk("sbus%d: ", num_sbus); | 
|  | sbus_clock = prom_getint(this_sbus, "clock-frequency"); | 
|  | if(sbus_clock == -1) | 
|  | sbus_clock = (25*1000*1000); | 
|  | printk("Clock %d.%d MHz\n", (int) ((sbus_clock/1000)/1000), | 
|  | (int) (((sbus_clock/1000)%1000 != 0) ? | 
|  | (((sbus_clock/1000)%1000) + 1000) : 0)); | 
|  |  | 
|  | prom_getstring(this_sbus, "name", | 
|  | sbus->prom_name, sizeof(sbus->prom_name)); | 
|  | sbus->clock_freq = sbus_clock; | 
|  | #ifdef CONFIG_SPARC32 | 
|  | if (sparc_cpu_model == sun4d) { | 
|  | sbus->devid = prom_getint(iommund, "device-id"); | 
|  | sbus->board = prom_getint(iommund, "board#"); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | sbus_bus_ranges_init(iommund, sbus); | 
|  |  | 
|  | sbus_devs = prom_getchild(this_sbus); | 
|  | if (!sbus_devs) { | 
|  | sbus->devices = NULL; | 
|  | goto next_bus; | 
|  | } | 
|  |  | 
|  | sbus->devices = kmalloc(sizeof(struct sbus_dev), GFP_ATOMIC); | 
|  |  | 
|  | this_dev = sbus->devices; | 
|  | this_dev->next = NULL; | 
|  |  | 
|  | this_dev->bus = sbus; | 
|  | this_dev->parent = NULL; | 
|  | fill_sbus_device(sbus_devs, this_dev); | 
|  |  | 
|  | /* Should we traverse for children? */ | 
|  | if(prom_getchild(sbus_devs)) { | 
|  | /* Allocate device node */ | 
|  | this_dev->child = kmalloc(sizeof(struct sbus_dev), | 
|  | GFP_ATOMIC); | 
|  | /* Fill it */ | 
|  | this_dev->child->bus = sbus; | 
|  | this_dev->child->next = NULL; | 
|  | fill_sbus_device(prom_getchild(sbus_devs), | 
|  | this_dev->child); | 
|  | sbus_do_child_siblings(prom_getchild(sbus_devs), | 
|  | this_dev->child, | 
|  | this_dev, | 
|  | sbus); | 
|  | } else { | 
|  | this_dev->child = NULL; | 
|  | } | 
|  |  | 
|  | while((sbus_devs = prom_getsibling(sbus_devs)) != 0) { | 
|  | /* Allocate device node */ | 
|  | this_dev->next = kmalloc(sizeof(struct sbus_dev), | 
|  | GFP_ATOMIC); | 
|  | this_dev = this_dev->next; | 
|  | this_dev->next = NULL; | 
|  |  | 
|  | /* Fill it */ | 
|  | this_dev->bus = sbus; | 
|  | this_dev->parent = NULL; | 
|  | fill_sbus_device(sbus_devs, this_dev); | 
|  |  | 
|  | /* Is there a child node hanging off of us? */ | 
|  | if(prom_getchild(sbus_devs)) { | 
|  | /* Get new device struct */ | 
|  | this_dev->child = kmalloc(sizeof(struct sbus_dev), | 
|  | GFP_ATOMIC); | 
|  | /* Fill it */ | 
|  | this_dev->child->bus = sbus; | 
|  | this_dev->child->next = NULL; | 
|  | fill_sbus_device(prom_getchild(sbus_devs), | 
|  | this_dev->child); | 
|  | sbus_do_child_siblings(prom_getchild(sbus_devs), | 
|  | this_dev->child, | 
|  | this_dev, | 
|  | sbus); | 
|  | } else { | 
|  | this_dev->child = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Walk all devices and apply parent ranges. */ | 
|  | sbus_fixup_all_regs(sbus->devices); | 
|  |  | 
|  | dvma_init(sbus); | 
|  | next_bus: | 
|  | num_sbus++; | 
|  | if(sparc_cpu_model == sun4u) { | 
|  | this_sbus = prom_getsibling(this_sbus); | 
|  | if(!this_sbus) | 
|  | break; | 
|  | this_sbus = prom_searchsiblings(this_sbus, "sbus"); | 
|  | } else if(sparc_cpu_model == sun4d) { | 
|  | iommund = prom_getsibling(iommund); | 
|  | if(!iommund) | 
|  | break; | 
|  | iommund = prom_searchsiblings(iommund, "io-unit"); | 
|  | if(!iommund) | 
|  | break; | 
|  | this_sbus = prom_searchsiblings(prom_getchild(iommund), "sbi"); | 
|  | } else { | 
|  | this_sbus = prom_getsibling(this_sbus); | 
|  | if(!this_sbus) | 
|  | break; | 
|  | this_sbus = prom_searchsiblings(this_sbus, "sbus"); | 
|  | } | 
|  | if(this_sbus) { | 
|  | sbus->next = kmalloc(sizeof(struct sbus_bus), GFP_ATOMIC); | 
|  | sbus = sbus->next; | 
|  | sbus->next = NULL; | 
|  | sbus->prom_node = this_sbus; | 
|  | } else { | 
|  | break; | 
|  | } | 
|  | } /* while(this_sbus) */ | 
|  |  | 
|  | if (sparc_cpu_model == sun4d) { | 
|  | extern void sun4d_init_sbi_irq(void); | 
|  | sun4d_init_sbi_irq(); | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_SPARC64 | 
|  | if (sparc_cpu_model == sun4u) { | 
|  | firetruck_init(); | 
|  | } | 
|  | #endif | 
|  | #ifdef CONFIG_SUN_AUXIO | 
|  | if (sparc_cpu_model == sun4u) | 
|  | auxio_probe (); | 
|  | #endif | 
|  | #ifdef CONFIG_SPARC64 | 
|  | if (sparc_cpu_model == sun4u) { | 
|  | extern void clock_probe(void); | 
|  |  | 
|  | clock_probe(); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | subsys_initcall(sbus_init); |