From benh at kernel.crashing.org Mon Jan 2 13:04:44 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Mon, 02 Jan 2006 13:04:44 +1100 Subject: [PATCH] powerpc: more g5 overtemp problem fix Message-ID: <1136167484.24205.8.camel@localhost.localdomain> Some G5s still occasionally experience shutdowns due to overtemp conditions despite the recent fix. After analyzing logs from such machines, it appears that the overtemp code is a bit too quick at shutting the machine down when reaching the critical temperature (tmax + 8) and doesn't leave the fan enough time to actually cool it down. This happens if the temperature of a CPU suddenly rises too high in a very short period of time, or occasionally on boot (that is the CPUs are already overtemp by the time the driver loads). This patches makes the code a bit more relaxed, leaving a few seconds to the fans to do their job before kicking the machine shutown. Signed-off-by: Benjamin Herrenschmidt --- linux-work.orig/drivers/macintosh/therm_pm72.c 2005-09-26 11:48:36.000000000 +1000 +++ linux-work/drivers/macintosh/therm_pm72.c 2005-10-07 11:03:41.000000000 +1000 @@ -923,7 +923,7 @@ if (temp_combi >= ((state0->mpu.tmax + 8) << 16)) { printk(KERN_WARNING "Warning ! Temperature way above maximum (%d) !\n", temp_combi >> 16); - state0->overtemp = CPU_MAX_OVERTEMP; + state0->overtemp += CPU_MAX_OVERTEMP / 4; } else if (temp_combi > (state0->mpu.tmax << 16)) state0->overtemp++; else @@ -998,7 +998,7 @@ printk(KERN_WARNING "Warning ! CPU %d temperature way above maximum" " (%d) !\n", state->index, temp >> 16); - state->overtemp = CPU_MAX_OVERTEMP; + state->overtemp += CPU_MAX_OVERTEMP / 4; } else if (temp > (state->mpu.tmax << 16)) state->overtemp++; else @@ -1060,7 +1060,7 @@ printk(KERN_WARNING "Warning ! CPU %d temperature way above maximum" " (%d) !\n", state->index, temp >> 16); - state->overtemp = CPU_MAX_OVERTEMP; + state->overtemp = CPU_MAX_OVERTEMP / 4; } else if (temp > (state->mpu.tmax << 16)) state->overtemp++; else From miltonm at bga.com Mon Jan 2 17:51:23 2006 From: miltonm at bga.com (Milton Miller) Date: Mon, 2 Jan 2006 00:51:23 -0600 (CST) Subject: UP FP restore fastpath missing Message-ID: <200601020651.k026pNMi011016@sullivan.realtime.net> I was reading the current powerpc process.c and noticed that __switch_to !CONFIG_SMP has code to avoid talking altivec and spe exceptions but not fp exceptions. milton From maule at sgi.com Tue Jan 3 14:22:49 2006 From: maule at sgi.com (Mark Maule) Date: Mon, 2 Jan 2006 21:22:49 -0600 Subject: [PATCH 0/3] msi abstractions and support for altix In-Reply-To: <20051222205023.GK2361@parisc-linux.org> References: <20051222201651.2019.37913.96422@lnx-maule.americas.sgi.com> <20051222202259.GA4959@suse.de> <20051222202627.GI17552@sgi.com> <20051222203415.GA28240@suse.de> <20051222203824.GJ17552@sgi.com> <20051222205023.GK2361@parisc-linux.org> Message-ID: <20060103032249.GA4957@sgi.com> On Thu, Dec 22, 2005 at 01:50:23PM -0700, Matthew Wilcox wrote: > On Thu, Dec 22, 2005 at 02:38:24PM -0600, Mark Maule wrote: > > Because on ia64 IA64_FIRST_DEVICE_VECTOR and IA64_LAST_DEVICE_VECTOR > > (from which MSI FIRST_DEVICE_VECTOR/LAST_DEVICE_VECTOR are derived) are not > > constants. The are now global variables (see change to asm-ia64/hw_irq.h) > > to allow the platform to override them. Altix uses a reduced range of > > vectors for devices, and this change was necessary to make assign_irq_vector() > > to work on altix. > > To be honest, I think this is just adding a third layer of paper over > the crack in the wall. The original code assumed x86; the ia64 port > added enough emulation to make it look like x86 and now altix fixes a > couple of assumptions. I say: bleh. > > What we actually need is an interface provided by the architecture that > allocates a new irq. I have a hankering to implement MSI on PA-RISC but > haven't found the time ... Matt, Greg, et. al: Did you guys have something in mind for a vector allocation interface? It seems to me that assign_irq_vector() more or less does what we want, but what is missing is a way for the platform to prime which vectors are available to choose from. One possibly better solution would be to call something in the init_IRQ path that would set up the vector pool available to assign_irq_vector(). Any opinions on this? I would maintain that this effort should be done independently of this patchset. thanks Mark From gregkh at suse.de Tue Jan 3 17:07:19 2006 From: gregkh at suse.de (Greg KH) Date: Mon, 2 Jan 2006 22:07:19 -0800 Subject: [PATCH 0/3] msi abstractions and support for altix In-Reply-To: <20060103032249.GA4957@sgi.com> References: <20051222201651.2019.37913.96422@lnx-maule.americas.sgi.com> <20051222202259.GA4959@suse.de> <20051222202627.GI17552@sgi.com> <20051222203415.GA28240@suse.de> <20051222203824.GJ17552@sgi.com> <20051222205023.GK2361@parisc-linux.org> <20060103032249.GA4957@sgi.com> Message-ID: <20060103060719.GA1845@suse.de> On Mon, Jan 02, 2006 at 09:22:49PM -0600, Mark Maule wrote: > On Thu, Dec 22, 2005 at 01:50:23PM -0700, Matthew Wilcox wrote: > > On Thu, Dec 22, 2005 at 02:38:24PM -0600, Mark Maule wrote: > > > Because on ia64 IA64_FIRST_DEVICE_VECTOR and IA64_LAST_DEVICE_VECTOR > > > (from which MSI FIRST_DEVICE_VECTOR/LAST_DEVICE_VECTOR are derived) are not > > > constants. The are now global variables (see change to asm-ia64/hw_irq.h) > > > to allow the platform to override them. Altix uses a reduced range of > > > vectors for devices, and this change was necessary to make assign_irq_vector() > > > to work on altix. > > > > To be honest, I think this is just adding a third layer of paper over > > the crack in the wall. The original code assumed x86; the ia64 port > > added enough emulation to make it look like x86 and now altix fixes a > > couple of assumptions. I say: bleh. > > > > What we actually need is an interface provided by the architecture that > > allocates a new irq. I have a hankering to implement MSI on PA-RISC but > > haven't found the time ... > > Matt, Greg, et. al: > > Did you guys have something in mind for a vector allocation interface? It > seems to me that assign_irq_vector() more or less does what we want, > but what is missing is a way for the platform to prime which vectors > are available to choose from. > > One possibly better solution would be to call something in the init_IRQ path > that would set up the vector pool available to assign_irq_vector(). > > Any opinions on this? I would maintain that this effort should be done > independently of this patchset. Care to write a patch showing how this would work? And why would this be independant of your other changes? thanks, greg k-h From olh at suse.de Wed Jan 4 06:58:31 2006 From: olh at suse.de (Olaf Hering) Date: Tue, 3 Jan 2006 20:58:31 +0100 Subject: [PATCH 4/11] powerpc: Add CONFIG_CRASH_DUMP In-Reply-To: <20051205003942.A65C468851@ozlabs.org> References: <1133743149.268607.418162138937.qpush@concordia> <20051205003942.A65C468851@ozlabs.org> Message-ID: <20060103195831.GA2898@suse.de> On Sun, Dec 04, Michael Ellerman wrote: > This patch adds a Kconfig variable, CONFIG_CRASH_DUMP, which configures the > built kernel for use as a Kdump kernel. > > Currently "all" this involves is changing the value of KERNELBASE to 32 MB. > +++ kexec/include/asm-powerpc/page.h > @@ -37,8 +37,15 @@ > */ > #define PAGE_MASK (~((1 << PAGE_SHIFT) - 1)) > > +#ifdef CONFIG_CRASH_DUMP > +/* Kdump kernel runs at 32 MB, change at your peril. */ > +#define PHYSICAL_START 0x2000000 > +#else > +#define PHYSICAL_START 0x0 > +#endif > + > #define PAGE_OFFSET ASM_CONST(CONFIG_KERNEL_START) > -#define KERNELBASE PAGE_OFFSET > +#define KERNELBASE (PAGE_OFFSET + PHYSICAL_START) This changes the vmlinux entry point, and breaks some assumptions in yaboot. It wont load an initrd anymore, flat_vmlinux is always false. It means also that a SLES9 installation cant load a SLES10 kernel+initrd via yaboot. Fixing it now in my tree. -- short story of a lazy sysadmin: alias appserv=wotan From jschopp at austin.ibm.com Wed Jan 4 05:51:22 2006 From: jschopp at austin.ibm.com (Joel Schopp) Date: Tue, 03 Jan 2006 12:51:22 -0600 Subject: [PATCH] ppc64: htab_initialize_secondary cannot be marked __init In-Reply-To: <20051228234629.GA18479@krispykreme> References: <20051228234629.GA18479@krispykreme> Message-ID: <43BAC7AA.6010101@austin.ibm.com> > -void __init htab_initialize_secondary(void) > +void htab_initialize_secondary(void) Wouldn't __devinit be the right thing to do? From ak at suse.de Wed Jan 4 08:57:52 2006 From: ak at suse.de (Andi Kleen) Date: Tue, 3 Jan 2006 22:57:52 +0100 Subject: [ANNOUNCE] numactl 0.9 released Message-ID: <200601032257.53039.ak@suse.de> A new release of the numactl / libnuma package has been released ftp://ftp.suse.com/pub/people/ak/numa/numactl-0.9.tar.gz af9f10f1f65a88b3368c02157718aa58 numactl-0.9.tar.gz It consists of a numactl program to run other programs with a specific NUMA policy and a libnuma shared library ("NUMA API") to set NUMA policy in applications and some additional tools. The 0.8 release had some problems (in fact it didn't even build on !x86-64 without tweaks) so I did 0.9 early. I fixed all the known bugs, added some patches that were reported after releases and added some minor new features. The compile flags can be now changed with the standard CFLAGS=.. argument, no need anymore for OPT_CFLAGS (thanks Ian!) I tweaked numademo a bit and it should be more useful now. It has more stable measurements now and reports the the results in MB/s instead of the weird units used before. There is a new random test that tests the memory performance with randomized accesses to defeat any hardware prefetching. And ppc64 support should really work now (I hope at least) And numastat now knows how to wrap the display for a large number of nodes (a killer feature!) I hope to declare this numactl 1.0 soon unless any bad bugs are reported. Any feedback appreciated. -Andi Detailed changes since 0.8: - Get rid of bogus distance.o that broke compilation on !x86-64 (sorry) - Handle CFLAGS overriding without OPT_CFLAGS (Ian Wienand) - Fix up section of get/set_mempolicy (Ian Wienand) - When no NUMA available fall back to one global node instead of one node per CPU (Samuel Thibault) - Don't rely on architecture symbols for dependency generation - Use __powerpc__ to detect PPC/PPC64 - numastat: * wrap display properly with many nodes * display nodes in forward order * install manpage in `make install'. - remove bogus numamemcpy.c - numademo: * allow standalone compile, make streamlib optional * clean up output * change output unit to standard MB/s * compile with more optimization * add random pass to fool any prefetching (slow) - make numademo compileable outside source tree - use gettimeofday instead of time stamp counters in benchmarks - support valgrind in testsuite - other minor changes From galak at gate.crashing.org Wed Jan 4 09:15:21 2006 From: galak at gate.crashing.org (Kumar Gala) Date: Tue, 3 Jan 2006 16:15:21 -0600 (CST) Subject: [PATCH] powerpc: fixing compile issue with !CONFIG_PCI in legacy_serial.c Message-ID: Only build in support for ISA and PCI cases if we have enabled CONFIG_ISA and CONFIG_PCI. Additionally, isa_bridge is a global so we shouldn't use it a parameter name since it gets redefined to NULL when !CONFIG_PCI. Signed-off-by: Kumar Gala --- commit 010d770e4b04e159ce9841b4572224579b26ae22 tree a04b75607365053bf289edc0b76923bb86b06b5c parent 8e80181ef9bf122ea5053e90cf9d9d0277c6e7ab author Kumar Gala Tue, 03 Jan 2006 16:19:45 -0600 committer Kumar Gala Tue, 03 Jan 2006 16:19:45 -0600 arch/powerpc/kernel/legacy_serial.c | 12 +++++++++++- 1 files changed, 11 insertions(+), 1 deletions(-) diff --git a/arch/powerpc/kernel/legacy_serial.c b/arch/powerpc/kernel/legacy_serial.c index 59164ba..f970ace 100644 --- a/arch/powerpc/kernel/legacy_serial.c +++ b/arch/powerpc/kernel/legacy_serial.c @@ -134,8 +134,9 @@ static int __init add_legacy_soc_port(st return add_legacy_port(np, -1, UPIO_MEM, addr, addr, NO_IRQ, flags); } +#ifdef CONFIG_ISA static int __init add_legacy_isa_port(struct device_node *np, - struct device_node *isa_bridge) + struct device_node *isa_brg) { u32 *reg; char *typep; @@ -167,7 +168,9 @@ static int __init add_legacy_isa_port(st return add_legacy_port(np, index, UPIO_PORT, reg[1], taddr, NO_IRQ, UPF_BOOT_AUTOCONF); } +#endif +#ifdef CONFIG_PCI static int __init add_legacy_pci_port(struct device_node *np, struct device_node *pci_dev) { @@ -233,6 +236,7 @@ static int __init add_legacy_pci_port(st */ return add_legacy_port(np, index, iotype, base, addr, NO_IRQ, UPF_BOOT_AUTOCONF); } +#endif /* * This is called very early, as part of setup_system() or eventually @@ -272,6 +276,7 @@ void __init find_legacy_serial_ports(voi of_node_put(soc); } +#ifdef CONFIG_ISA /* First fill our array with ISA ports */ for (np = NULL; (np = of_find_node_by_type(np, "serial"));) { struct device_node *isa = of_get_parent(np); @@ -282,7 +287,9 @@ void __init find_legacy_serial_ports(voi } of_node_put(isa); } +#endif +#ifdef CONFIG_PCI /* Next, try to locate PCI ports */ for (np = NULL; (np = of_find_all_nodes(np));) { struct device_node *pci, *parent = of_get_parent(np); @@ -312,6 +319,7 @@ void __init find_legacy_serial_ports(voi legacy_serial_console = index; of_node_put(parent); } +#endif DBG("legacy_serial_console = %d\n", legacy_serial_console); @@ -375,6 +383,7 @@ static void __init fixup_port_pio(int in struct device_node *np, struct plat_serial8250_port *port) { +#ifdef CONFIG_PCI struct pci_controller *hose; DBG("fixup_port_pio(%d)\n", index); @@ -391,6 +400,7 @@ static void __init fixup_port_pio(int in index, port->iobase, port->iobase + offset); port->iobase += offset; } +#endif } static void __init fixup_port_mmio(int index, From olof at lixom.net Wed Jan 4 09:20:12 2006 From: olof at lixom.net (Olof Johansson) Date: Tue, 3 Jan 2006 16:20:12 -0600 Subject: [PATCH] ppc64: htab_initialize_secondary cannot be marked __init In-Reply-To: <43BAC7AA.6010101@austin.ibm.com> References: <20051228234629.GA18479@krispykreme> <43BAC7AA.6010101@austin.ibm.com> Message-ID: <20060103222012.GB16278@pb15.lixom.net> On Tue, Jan 03, 2006 at 12:51:22PM -0600, Joel Schopp wrote: > >-void __init htab_initialize_secondary(void) > >+void htab_initialize_secondary(void) > > Wouldn't __devinit be the right thing to do? If anything it should be __cpuinit, but noone's gone through and marked up arch/powerpc for that at all yet so just marking a single function doesn't make much sense. -Olof From olh at suse.de Wed Jan 4 10:41:52 2006 From: olh at suse.de (Olaf Hering) Date: Wed, 4 Jan 2006 00:41:52 +0100 Subject: [PATCH 4/11] powerpc: Add CONFIG_CRASH_DUMP In-Reply-To: <43BB013F.6060403@us.ibm.com> References: <1133743149.268607.418162138937.qpush@concordia> <20051205003942.A65C468851@ozlabs.org> <20060103195831.GA2898@suse.de> <43BB013F.6060403@us.ibm.com> Message-ID: <20060103234152.GA9642@suse.de> On Tue, Jan 03, Haren Myneni wrote: > Yes, it could be a problem if we do OF boot at 32MB (CONFIG_CRASH_DUMP) > since the the initrd's location is set at 36MB by the yaboot. However, > in the kdump boot, the initrd location is not at fixed location and > changed by the kexec-tool. > > But, we found an issue (Oops) when we load the second kernel in to the > crash kernel region. During the first boot, the crash kernel region is > reserved (32M - 160M for crashkernel=128M at 32M) and the initrd is at 36M. > But, after loading the initrd, free_initrd() is freed initrd region even > though it is part of crash kernel reserved region. When we load the > second kernel using kexec-tool, we are copying into unallocated memory. > Thus caused panic. Why are all these values hardcoded? Cant this be probed at runtime somehow? I hope the only fixed address is the entry point of the inital bootloader (yaboot or zImage). I will finally change yaboot this week to not use hardcoded values anymore. -- short story of a lazy sysadmin: alias appserv=wotan From maule at sgi.com Wed Jan 4 10:50:24 2006 From: maule at sgi.com (Mark Maule) Date: Tue, 3 Jan 2006 17:50:24 -0600 Subject: [PATCH 1/3] msi vector targeting abstractions In-Reply-To: <20060103223918.GB13841@esmail.cup.hp.com> References: <20051222201651.2019.37913.96422@lnx-maule.americas.sgi.com> <20051222201657.2019.69251.48815@lnx-maule.americas.sgi.com> <20060103223918.GB13841@esmail.cup.hp.com> Message-ID: <20060103235024.GC16827@sgi.com> On Tue, Jan 03, 2006 at 02:39:18PM -0800, Grant Grundler wrote: > On Thu, Dec 22, 2005 at 02:15:49PM -0600, Mark Maule wrote: > > Abstract portions of the MSI core for platforms that do not use standard > > APIC interrupt controllers. This is implemented through a new arch-specific > > msi setup routine, and a set of msi ops which can be set on a per platform > > basis. > > ... > > + > > + msi_ops->target(vector, dest_cpu, &address_hi, &address_lo); > > + > > + pci_write_config_dword(entry->dev, msi_upper_address_reg(pos), > > + address_hi); > > pci_write_config_dword(entry->dev, msi_lower_address_reg(pos), > > - address.lo_address.value); > > + address_lo); > > set_native_irq_info(irq, cpu_mask); > > break; > > } > ... > > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > > +++ msi/drivers/pci/msi-apic.c 2005-12-22 11:09:37.022232088 -0600 > ... > > +struct msi_ops msi_apic_ops = { > > + .setup = msi_setup_apic, > > + .teardown = msi_teardown_apic, > > +#ifdef CONFIG_SMP > > + .target = msi_target_apic, > > +#endif > > Mark, > msi_target_apic() initializes address_lo parameter. > Even on a UP machine, we need inialize this value. Not sure what you mean here. target is used to retarget an existing MSI vector to a different processor. In the case of apic, this appears to be accomplished by swizzling the cpu in the low 32 bits of the msi address. Nothing needs to change in the upper 32 bits. > > If target is called unconditionally, wouldn't it be better > for msi_target_apic() always be called? target is called through the msi_ops->target vector. SN does not use msi_target_apic(), it uses sn_msi_target(). Other platforms can implement target however they need to. > > It would also be good for msi_target_apic to validate the 'dest_cpu' is online. > Maybe a BUG_ON or something like that. That wasn't a check in the original code flow ... the main protection appears to be in the upper levels in irq_affinity_write_proc(). > > grant > > ps. not done looking through this...and still curious to see where > other discussion about generic vector assignment leads. From iod00d at hp.com Wed Jan 4 09:39:18 2006 From: iod00d at hp.com (Grant Grundler) Date: Tue, 3 Jan 2006 14:39:18 -0800 Subject: [PATCH 1/3] msi vector targeting abstractions In-Reply-To: <20051222201657.2019.69251.48815@lnx-maule.americas.sgi.com> References: <20051222201651.2019.37913.96422@lnx-maule.americas.sgi.com> <20051222201657.2019.69251.48815@lnx-maule.americas.sgi.com> Message-ID: <20060103223918.GB13841@esmail.cup.hp.com> On Thu, Dec 22, 2005 at 02:15:49PM -0600, Mark Maule wrote: > Abstract portions of the MSI core for platforms that do not use standard > APIC interrupt controllers. This is implemented through a new arch-specific > msi setup routine, and a set of msi ops which can be set on a per platform > basis. ... > + > + msi_ops->target(vector, dest_cpu, &address_hi, &address_lo); > + > + pci_write_config_dword(entry->dev, msi_upper_address_reg(pos), > + address_hi); > pci_write_config_dword(entry->dev, msi_lower_address_reg(pos), > - address.lo_address.value); > + address_lo); > set_native_irq_info(irq, cpu_mask); > break; > } ... > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ msi/drivers/pci/msi-apic.c 2005-12-22 11:09:37.022232088 -0600 ... > +struct msi_ops msi_apic_ops = { > + .setup = msi_setup_apic, > + .teardown = msi_teardown_apic, > +#ifdef CONFIG_SMP > + .target = msi_target_apic, > +#endif Mark, msi_target_apic() initializes address_lo parameter. Even on a UP machine, we need inialize this value. If target is called unconditionally, wouldn't it be better for msi_target_apic() always be called? It would also be good for msi_target_apic to validate the 'dest_cpu' is online. Maybe a BUG_ON or something like that. grant ps. not done looking through this...and still curious to see where other discussion about generic vector assignment leads. From iod00d at hp.com Wed Jan 4 11:01:41 2006 From: iod00d at hp.com (Grant Grundler) Date: Tue, 3 Jan 2006 16:01:41 -0800 Subject: [PATCH 2/3] per-platform IA64_{FIRST, LAST}_DEVICE_VECTOR definitions In-Reply-To: <20051222201705.2019.59377.24060@lnx-maule.americas.sgi.com> References: <20051222201651.2019.37913.96422@lnx-maule.americas.sgi.com> <20051222201705.2019.59377.24060@lnx-maule.americas.sgi.com> Message-ID: <20060104000141.GC13841@esmail.cup.hp.com> On Thu, Dec 22, 2005 at 02:15:57PM -0600, Mark Maule wrote: > Abstract IA64_FIRST_DEVICE_VECTOR/IA64_LAST_DEVICE_VECTOR since SN platforms > use a subset of the IA64 range. Implement this by making the above macros > global variables which the platform can override in it setup code. ... > Index: msi/arch/ia64/sn/kernel/irq.c > =================================================================== > --- msi.orig/arch/ia64/sn/kernel/irq.c 2005-12-21 22:59:09.199823700 -0600 > +++ msi/arch/ia64/sn/kernel/irq.c 2005-12-22 14:10:01.024578027 -0600 > @@ -203,6 +203,9 @@ > int i; > irq_desc_t *base_desc = irq_desc; > > + ia64_first_device_vector = IA64_SN2_FIRST_DEVICE_VECTOR; > + ia64_last_device_vector = IA64_SN2_LAST_DEVICE_VECTOR; Shouldn't this chunk of diff go in "PATCH [2/3] altix: msi support"? (typo: that should have been "3/3" in the original mail) thanks, grant From iod00d at hp.com Wed Jan 4 11:20:47 2006 From: iod00d at hp.com (Grant Grundler) Date: Tue, 3 Jan 2006 16:20:47 -0800 Subject: [PATCH 1/3] msi vector targeting abstractions In-Reply-To: <20060103235024.GC16827@sgi.com> References: <20051222201651.2019.37913.96422@lnx-maule.americas.sgi.com> <20051222201657.2019.69251.48815@lnx-maule.americas.sgi.com> <20060103223918.GB13841@esmail.cup.hp.com> <20060103235024.GC16827@sgi.com> Message-ID: <20060104002047.GA14810@esmail.cup.hp.com> On Tue, Jan 03, 2006 at 05:50:24PM -0600, Mark Maule wrote: > > > +struct msi_ops msi_apic_ops = { > > > + .setup = msi_setup_apic, > > > + .teardown = msi_teardown_apic, > > > +#ifdef CONFIG_SMP > > > + .target = msi_target_apic, > > > +#endif > > > > Mark, > > msi_target_apic() initializes address_lo parameter. > > Even on a UP machine, we need inialize this value. > > Not sure what you mean here. target is used to retarget an existing > MSI vector to a different processor. Right - I didn't realize the caller, set_msi_affinity(), was surrounded by "#ifdef CONFIG_SMP". But set_msi_affinity() appears to be dead code. I couldn't find any calls to set_msi_affinity() in 2.6.14 or 2.6.15. Greg, you want a patch to remove that? thanks, grant From gregkh at suse.de Wed Jan 4 11:27:38 2006 From: gregkh at suse.de (Greg KH) Date: Tue, 3 Jan 2006 16:27:38 -0800 Subject: [PATCH 1/3] msi vector targeting abstractions In-Reply-To: <20060104002047.GA14810@esmail.cup.hp.com> References: <20051222201651.2019.37913.96422@lnx-maule.americas.sgi.com> <20051222201657.2019.69251.48815@lnx-maule.americas.sgi.com> <20060103223918.GB13841@esmail.cup.hp.com> <20060103235024.GC16827@sgi.com> <20060104002047.GA14810@esmail.cup.hp.com> Message-ID: <20060104002737.GA18963@suse.de> On Tue, Jan 03, 2006 at 04:20:47PM -0800, Grant Grundler wrote: > On Tue, Jan 03, 2006 at 05:50:24PM -0600, Mark Maule wrote: > > > > +struct msi_ops msi_apic_ops = { > > > > + .setup = msi_setup_apic, > > > > + .teardown = msi_teardown_apic, > > > > +#ifdef CONFIG_SMP > > > > + .target = msi_target_apic, > > > > +#endif > > > > > > Mark, > > > msi_target_apic() initializes address_lo parameter. > > > Even on a UP machine, we need inialize this value. > > > > Not sure what you mean here. target is used to retarget an existing > > MSI vector to a different processor. > > Right - I didn't realize the caller, set_msi_affinity(), was surrounded by > "#ifdef CONFIG_SMP". > > But set_msi_affinity() appears to be dead code. > I couldn't find any calls to set_msi_affinity() in 2.6.14 or 2.6.15. > Greg, you want a patch to remove that? Yes please, that would be great to have. thanks, greg k-h From haren at us.ibm.com Wed Jan 4 09:57:03 2006 From: haren at us.ibm.com (Haren Myneni) Date: Tue, 03 Jan 2006 14:57:03 -0800 Subject: [PATCH 4/11] powerpc: Add CONFIG_CRASH_DUMP In-Reply-To: <20060103195831.GA2898@suse.de> References: <1133743149.268607.418162138937.qpush@concordia> <20051205003942.A65C468851@ozlabs.org> <20060103195831.GA2898@suse.de> Message-ID: <43BB013F.6060403@us.ibm.com> Olaf Hering wrote: > On Sun, Dec 04, Michael Ellerman wrote: > > > >>This patch adds a Kconfig variable, CONFIG_CRASH_DUMP, which configures the >>built kernel for use as a Kdump kernel. >> >>Currently "all" this involves is changing the value of KERNELBASE to 32 MB. >> >> > > > >>+++ kexec/include/asm-powerpc/page.h >>@@ -37,8 +37,15 @@ >> */ >> #define PAGE_MASK (~((1 << PAGE_SHIFT) - 1)) >> >>+#ifdef CONFIG_CRASH_DUMP >>+/* Kdump kernel runs at 32 MB, change at your peril. */ >>+#define PHYSICAL_START 0x2000000 >>+#else >>+#define PHYSICAL_START 0x0 >>+#endif >>+ >> #define PAGE_OFFSET ASM_CONST(CONFIG_KERNEL_START) >>-#define KERNELBASE PAGE_OFFSET >>+#define KERNELBASE (PAGE_OFFSET + PHYSICAL_START) >> >> > >This changes the vmlinux entry point, and breaks some assumptions in >yaboot. It wont load an initrd anymore, flat_vmlinux is always false. > >It means also that a SLES9 installation cant load a SLES10 kernel+initrd via yaboot. >Fixing it now in my tree. > > > Yes, it could be a problem if we do OF boot at 32MB (CONFIG_CRASH_DUMP) since the the initrd's location is set at 36MB by the yaboot. However, in the kdump boot, the initrd location is not at fixed location and changed by the kexec-tool. But, we found an issue (Oops) when we load the second kernel in to the crash kernel region. During the first boot, the crash kernel region is reserved (32M - 160M for crashkernel=128M at 32M) and the initrd is at 36M. But, after loading the initrd, free_initrd() is freed initrd region even though it is part of crash kernel reserved region. When we load the second kernel using kexec-tool, we are copying into unallocated memory. Thus caused panic. One solution is move the initrd during prom_init. But, prom_claim is failed if try to claim outside of first memory node (> rmo_top) on my P5 machine. The other one could be move the initrd later. Since we are freeing the initrd anyway during early boot (before we load the second kernel), it will be an extra step of moving initrd (allocating memory and copy). The following patch could fix this issue - free only regions that are not part of crash region. As this issue may also exists on other archs, made changes in the init/initramfs.c. Is there any better way to fix this problem? Thanks Haren -------------- next part -------------- A non-text attachment was scrubbed... Name: initrd_kdump_fix.patch Type: text/x-patch Size: 1708 bytes Desc: not available Url : http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060103/0260d115/attachment.bin From maule at sgi.com Wed Jan 4 14:52:16 2006 From: maule at sgi.com (Mark Maule) Date: Tue, 3 Jan 2006 21:52:16 -0600 Subject: [PATCH 1/3] msi vector targeting abstractions In-Reply-To: <20060104002737.GA18963@suse.de> References: <20051222201651.2019.37913.96422@lnx-maule.americas.sgi.com> <20051222201657.2019.69251.48815@lnx-maule.americas.sgi.com> <20060103223918.GB13841@esmail.cup.hp.com> <20060103235024.GC16827@sgi.com> <20060104002047.GA14810@esmail.cup.hp.com> <20060104002737.GA18963@suse.de> Message-ID: <20060104035216.GD16827@sgi.com> On Tue, Jan 03, 2006 at 04:27:38PM -0800, Greg KH wrote: > On Tue, Jan 03, 2006 at 04:20:47PM -0800, Grant Grundler wrote: > > On Tue, Jan 03, 2006 at 05:50:24PM -0600, Mark Maule wrote: > > > > > +struct msi_ops msi_apic_ops = { > > > > > + .setup = msi_setup_apic, > > > > > + .teardown = msi_teardown_apic, > > > > > +#ifdef CONFIG_SMP > > > > > + .target = msi_target_apic, > > > > > +#endif > > > > > > > > Mark, > > > > msi_target_apic() initializes address_lo parameter. > > > > Even on a UP machine, we need inialize this value. > > > > > > Not sure what you mean here. target is used to retarget an existing > > > MSI vector to a different processor. > > > > Right - I didn't realize the caller, set_msi_affinity(), was surrounded by > > "#ifdef CONFIG_SMP". > > > > But set_msi_affinity() appears to be dead code. > > I couldn't find any calls to set_msi_affinity() in 2.6.14 or 2.6.15. > > Greg, you want a patch to remove that? > > Yes please, that would be great to have. > > thanks, > > greg k-h Is that really dead code? From msi.h: #ifdef CONFIG_SMP #define set_msi_irq_affinity set_msi_affinity #else #define set_msi_irq_affinity NULL #endif Mark From haren at us.ibm.com Wed Jan 4 16:06:31 2006 From: haren at us.ibm.com (Haren Myneni) Date: Tue, 03 Jan 2006 21:06:31 -0800 Subject: [PATCH 4/11] powerpc: Add CONFIG_CRASH_DUMP In-Reply-To: <20060103234152.GA9642@suse.de> References: <1133743149.268607.418162138937.qpush@concordia> <20051205003942.A65C468851@ozlabs.org> <20060103195831.GA2898@suse.de> <43BB013F.6060403@us.ibm.com> <20060103234152.GA9642@suse.de> Message-ID: <43BB57D7.1050004@us.ibm.com> Olaf Hering wrote: > On Tue, Jan 03, Haren Myneni wrote: > > > >>Yes, it could be a problem if we do OF boot at 32MB (CONFIG_CRASH_DUMP) >>since the the initrd's location is set at 36MB by the yaboot. However, >>in the kdump boot, the initrd location is not at fixed location and >>changed by the kexec-tool. >> >>But, we found an issue (Oops) when we load the second kernel in to the >>crash kernel region. During the first boot, the crash kernel region is >>reserved (32M - 160M for crashkernel=128M at 32M) and the initrd is at 36M. >>But, after loading the initrd, free_initrd() is freed initrd region even >>though it is part of crash kernel reserved region. When we load the >>second kernel using kexec-tool, we are copying into unallocated memory. >>Thus caused panic. >> >> > >Why are all these values hardcoded? Cant this be probed at runtime >somehow? I hope the only fixed address is the entry point of the inital >bootloader (yaboot or zImage). >I will finally change yaboot this week to not use hardcoded values >anymore. > > > > I am not sure whether yaboot can find the initrd location at runtime. If the yaboot can probe and find the crashkernel end at run time, then we might be considering 2 issues: - At present, alloc_bottom is moved after initrd_end. But it has to be before rmo_top. Hence, need changes in prom_init.c. we also need to make sure that sufficient memory is available before rmo_top for RTAS and reserve map. Otherwise prom_claim() will get failed. Right? - As you pointed out before (minor one), the existing yaboot will not work with the new kernel. Ex: SLES9's yaboot for SLES10 kernel. From rsa at us.ibm.com Wed Jan 4 17:32:17 2006 From: rsa at us.ibm.com (Ryan Arnold) Date: Wed, 04 Jan 2006 00:32:17 -0600 Subject: [RFC PATCH 1/1] fix get_ & put_ chars in hvc_vio to fix IN/OUT _BUF assumptions In-Reply-To: <858c3c666b9ac1fd8ffd01709426114e@bga.com> References: <20051217001031.456315000@localhost> <20051217002255.601962000@localhost> <858c3c666b9ac1fd8ffd01709426114e@bga.com> Message-ID: <1136356337.9010.20.camel@localhost.localdomain> Greetings Milton, et al. On Fri, 2005-12-16 at 23:02 -0600, Milton Miller wrote: > > + > > +/* > > + * This is a design shortcoming, the number '16' is a vio required > > buffer > > + * size. This should be changeable per architecture, but hvc_struct > > relies > > + * upon it and that struct is used by all hvc_console backend > > drivers. This > > + * needs to be fixed. > > + */ > > This is a bit strong. vio requires inbuf to be at least 16, and will > process upto 16 in outbound. They could be bigger, it will only cause > the hvc_driver to loop. Outbound couuld be smaller, but it would > reduce the efficency. I wrote this comment. In retrospect it is definitely too strong. None-the-less I feel the need to address a few dangerous assumptions in the hvc_vio code based upon the size of N_INBUF and N_OUTBUF. The firmware function plpar_hcall_norets(H_PUT_TERM_CHAR, ...) will not gracefully accept a 'count' parameter exceeding '16'. It will generate an H_Parameter error. The current hvconsole.c hvc_put_chars() function does not account for this and is prone to breakage should N_OUTBUF have a size greater than '16'. Furthermore, the hvc_console driver asks its back-ends to read 'count' number of characters based upon the amount of room left in the flip buffer. This value is less-than-or-equal-to the size of N_INBUF. Presently the hvc_vio driver makes an erroneous assumption that it can forward this request directly to vio firmware and ask for a particular number of bytes to be read into its recv buffer. This is not how plpar_hcall(H_GET_TERM_CHAR,...) works. You don't ask it to read a number of bytes. After is invoked it tells you how many bytes it read, up to the firmware specified maximum of 16. Should the amount requested (count) be less than the number read from firmware we'd over-write the flip buffer. We've not noticed any problems thus-far because we currently allow the tty to drain the flip buffer before it gets too full (after every 64 bytes read). Draining after 64 bytes is unnecessary considering that the flip buffer is 512 bytes so I've removed it. To fix the back-end problem I've directed the back-end drivers to return -EAGAIN if they can't satisfy the get request size-constraint. This signals the hvc_console driver front-end that it should allow the tty to clear the flip buffer to make room for a larger 'get'. This allows us to pack the flip buffer to be nearly full. As requested, I've also removed the front-end function __hvc_write_kernel() because it is no longer necessary. The following patch is against powerpc.git Ryan S. Arnold IBM Linux Technology Center Signed-off-by: "Ryan S. Arnold" -------------- next part -------------- A non-text attachment was scrubbed... Name: powerpc.git.hvc_console.patch Type: text/x-patch Size: 4325 bytes Desc: not available Url : http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/bd56f19f/attachment.bin From msdemlei at cl.uni-heidelberg.de Thu Jan 5 01:20:06 2006 From: msdemlei at cl.uni-heidelberg.de (Markus Demleitner) Date: Wed, 4 Jan 2006 15:20:06 +0100 Subject: Phantom pain with windfarm on diskless iMac G5 Message-ID: <20060104142006.GA8782@victor.cl.uni-heidelberg.de> Hi, I tried 2.6.15 on my diskless iMac G5 clients today, resulting in 747 emulation mode (vrooom...!). It turns out windfarm was querying the hard disk temperature sensor, which usually is mounted on the mounting bracket Apple uses. We made the mistake of removing these (from about 30 machines:-(), which in turn made windfarm_lm75_sensor.c:wf_lm75_get return ffff, which translates into about 255 degrees celsius. No wonder windfarm pumped like there's no tomorrow. I've "fixed" this by returning some fixed low temperature if I see ffff in wf_lm75_get for now, but I *guess* it would be nice to have some way to detect the absence of the sensor (and tell it from a simple failure). However, the OF device trees still list the sensor and even the hard disk itself even on the diskless machines. Even if there were a way to detect the absence of the sensor, there's still the problem that windfarm_pm81.c insists on having a hd temp sensor to work, so a fix would probably require spoiling that wonderful if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp) in there and replacing it with something like if (sensor_cpu_power && sensor_cpu_temp && (machine_has_hd() && sensor_hd_temp)) where I have no idea how to implement machine_has_hd(). A further similar hack would spoil wf_smu_sys_fans_tick, and uglyness prevails. In short: Am I doomed to hack the kernels of my diskless clients to eternity (or retrofit the sensors)? Or is there a sane way to treat that kind of problem? Thanks, Markus From arnd at arndb.de Thu Jan 5 06:55:53 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 4 Jan 2006 19:55:53 +0000 Subject: [PATCH, version 7] cell: enable pause(0) in cpu_idle In-Reply-To: <200512201314.12932.arnd@arndb.de> References: <200512171228.21578.arnd@arndb.de> <200512201314.12932.arnd@arndb.de> Message-ID: <200601041955.53577.arnd@arndb.de> This patch enables support for pause(0) power management state for the Cell Broadband Processor, which is import for power efficient operation. The pervasive infrastructure will in the future enable us to introduce more functionality specific to the Cell's pervasive unit. From: Maximino Aguilar Signed-off-by: Arnd Bergmann --- The only comment I got for version 6 was about formatting of C style comments and I fixed those up, so let's hope this is the final version of the patch. Please apply to powerpc.git. diffstat: arch/powerpc/kernel/cputable.c | 2 arch/powerpc/kernel/traps.c | 6 arch/powerpc/platforms/cell/Makefile | 2 arch/powerpc/platforms/cell/pervasive.c | 229 ++++++++++++++++++++++ arch/powerpc/platforms/cell/pervasive.h | 62 +++++ arch/powerpc/platforms/cell/setup.c | 2 arch/powerpc/platforms/pseries/ras.c | 5 arch/powerpc/platforms/pseries/ras.h | 9 arch/powerpc/platforms/pseries/setup.c | 4 include/asm-powerpc/cputable.h | 4 include/asm-powerpc/machdep.h | 2 include/asm-powerpc/reg.h | 22 +- 12 files changed, 335 insertions(+), 14 deletions(-) Arnd <>< Index: linux-2.6.15-rc/arch/powerpc/platforms/cell/Makefile =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/platforms/cell/Makefile +++ linux-2.6.15-rc/arch/powerpc/platforms/cell/Makefile @@ -1,4 +1,6 @@ obj-y += interrupt.o iommu.o setup.o spider-pic.o +obj-y += pervasive.o + obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_SPU_FS) += spufs/ spu_base.o builtin-spufs-$(CONFIG_SPU_FS) += spu_syscalls.o Index: linux-2.6.15-rc/arch/powerpc/platforms/cell/pervasive.c =================================================================== --- /dev/null +++ linux-2.6.15-rc/arch/powerpc/platforms/cell/pervasive.c @@ -0,0 +1,229 @@ +/* + * CBE Pervasive Monitor and Debug + * + * (C) Copyright IBM Corporation 2005 + * + * Authors: Maximino Aguilar (maguilar at us.ibm.com) + * Michael N. Day (mnday at us.ibm.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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#undef DEBUG + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "pervasive.h" + +static DEFINE_SPINLOCK(cbe_pervasive_lock); +struct cbe_pervasive { + struct pmd_regs __iomem *regs; + unsigned int thread; +}; + +/* can't use per_cpu from setup_arch */ +static struct cbe_pervasive cbe_pervasive[NR_CPUS]; + +static void __init cbe_enable_pause_zero(void) +{ + unsigned long thread_switch_control; + unsigned long temp_register; + struct cbe_pervasive *p; + int thread; + + spin_lock_irq(&cbe_pervasive_lock); + p = &cbe_pervasive[smp_processor_id()]; + + if (!cbe_pervasive->regs) + goto out; + + pr_debug("Power Management: CPU %d\n", smp_processor_id()); + + /* Enable Pause(0) control bit */ + temp_register = in_be64(&p->regs->pm_control); + + out_be64(&p->regs->pm_control, + temp_register|PMD_PAUSE_ZERO_CONTROL); + + /* Enable DEC and EE interrupt request */ + thread_switch_control = mfspr(SPRN_TSC_CELL); + thread_switch_control |= TSC_CELL_EE_ENABLE | TSC_CELL_EE_BOOST; + + switch ((mfspr(SPRN_CTRLF) & CTRL_CT)) { + case CTRL_CT0: + thread_switch_control |= TSC_CELL_DEC_ENABLE_0; + thread = 0; + break; + case CTRL_CT1: + thread_switch_control |= TSC_CELL_DEC_ENABLE_1; + thread = 1; + break; + default: + printk(KERN_WARNING "%s: unknown configuration\n", + __FUNCTION__); + thread = -1; + break; + } + + if (p->thread != thread) + printk(KERN_WARNING "%s: device tree inconsistant, " + "cpu %i: %d/%d\n", __FUNCTION__, + smp_processor_id(), + p->thread, thread); + + mtspr(SPRN_TSC_CELL, thread_switch_control); + +out: + spin_unlock_irq(&cbe_pervasive_lock); +} + +static void cbe_idle(void) +{ + unsigned long ctrl; + + cbe_enable_pause_zero(); + + while (1) { + if (!need_resched()) { + local_irq_disable(); + while (!need_resched()) { + /* go into low thread priority */ + HMT_low(); + + /* + * atomically disable thread execution + * and runlatch. + * External and Decrementer exceptions + * are still handled when the thread + * is disabled but now enter in + * cbe_system_reset_exception() + */ + ctrl = mfspr(SPRN_CTRLF); + ctrl &= ~(CTRL_RUNLATCH | CTRL_TE); + mtspr(SPRN_CTRLT, ctrl); + } + /* restore thread prio */ + HMT_medium(); + local_irq_enable(); + } + + /* + * turn runlatch on again before scheduling the + * process we just woke up + */ + ppc64_runlatch_on(); + + preempt_enable_no_resched(); + schedule(); + preempt_disable(); + } +} + +int cbe_system_reset_exception(struct pt_regs *regs) +{ + switch (regs->msr & SRR1_WAKEMASK) { + case SRR1_WAKEEE: + do_IRQ(regs); + break; + case SRR1_WAKEDEC: + timer_interrupt(regs); + break; + case SRR1_WAKEMT: + /* no action required */ + break; + default: + /* do system reset */ + return 0; + } + /* everything handled */ + return 1; +} + +static int __init cbe_find_pmd_mmio(int cpu, struct cbe_pervasive *p) +{ + struct device_node *node; + unsigned int *int_servers; + char *addr; + unsigned long real_address; + unsigned int size; + + struct pmd_regs __iomem *pmd_mmio_area; + int hardid, thread; + int proplen; + + pmd_mmio_area = NULL; + hardid = get_hard_smp_processor_id(cpu); + for (node = NULL; (node = of_find_node_by_type(node, "cpu"));) { + int_servers = (void *) get_property(node, + "ibm,ppc-interrupt-server#s", &proplen); + if (!int_servers) { + printk(KERN_WARNING "%s misses " + "ibm,ppc-interrupt-server#s property", + node->full_name); + continue; + } + for (thread = 0; thread < proplen / sizeof (int); thread++) { + if (hardid == int_servers[thread]) { + addr = get_property(node, "pervasive", NULL); + goto found; + } + } + } + + printk(KERN_WARNING "%s: CPU %d not found\n", __FUNCTION__, cpu); + return -EINVAL; + +found: + real_address = *(unsigned long*) addr; + addr += sizeof (unsigned long); + size = *(unsigned int*) addr; + + pr_debug("pervasive area for CPU %d at %lx, size %x\n", + cpu, real_address, size); + p->regs = __ioremap(real_address, size, _PAGE_NO_CACHE); + p->thread = thread; + return 0; +} + +void __init cell_pervasive_init(void) +{ + struct cbe_pervasive *p; + int cpu; + int ret; + + if (!cpu_has_feature(CPU_FTR_PAUSE_ZERO)) + return; + + for_each_cpu(cpu) { + p = &cbe_pervasive[cpu]; + ret = cbe_find_pmd_mmio(cpu, p); + if (ret) + return; + } + + ppc_md.idle_loop = cbe_idle; + ppc_md.system_reset_exception = cbe_system_reset_exception; +} Index: linux-2.6.15-rc/arch/powerpc/platforms/cell/pervasive.h =================================================================== --- /dev/null +++ linux-2.6.15-rc/arch/powerpc/platforms/cell/pervasive.h @@ -0,0 +1,62 @@ +/* + * Cell Pervasive Monitor and Debug interface and HW structures + * + * (C) Copyright IBM Corporation 2005 + * + * Authors: Maximino Aguilar (maguilar at us.ibm.com) + * David J. Erb (djerb at us.ibm.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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#ifndef PERVASIVE_H +#define PERVASIVE_H + +struct pmd_regs { + u8 pad_0x0000_0x0800[0x0800 - 0x0000]; /* 0x0000 */ + + /* Thermal Sensor Registers */ + u64 ts_ctsr1; /* 0x0800 */ + u64 ts_ctsr2; /* 0x0808 */ + u64 ts_mtsr1; /* 0x0810 */ + u64 ts_mtsr2; /* 0x0818 */ + u64 ts_itr1; /* 0x0820 */ + u64 ts_itr2; /* 0x0828 */ + u64 ts_gitr; /* 0x0830 */ + u64 ts_isr; /* 0x0838 */ + u64 ts_imr; /* 0x0840 */ + u64 tm_cr1; /* 0x0848 */ + u64 tm_cr2; /* 0x0850 */ + u64 tm_simr; /* 0x0858 */ + u64 tm_tpr; /* 0x0860 */ + u64 tm_str1; /* 0x0868 */ + u64 tm_str2; /* 0x0870 */ + u64 tm_tsr; /* 0x0878 */ + + /* Power Management */ + u64 pm_control; /* 0x0880 */ +#define PMD_PAUSE_ZERO_CONTROL 0x10000 + u64 pm_status; /* 0x0888 */ + + /* Time Base Register */ + u64 tbr; /* 0x0890 */ + + u8 pad_0x0898_0x1000 [0x1000 - 0x0898]; /* 0x0898 */ +}; + +void __init cell_pervasive_init(void); + +#endif Index: linux-2.6.15-rc/arch/powerpc/platforms/cell/setup.c =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/platforms/cell/setup.c +++ linux-2.6.15-rc/arch/powerpc/platforms/cell/setup.c @@ -49,6 +49,7 @@ #include "interrupt.h" #include "iommu.h" +#include "pervasive.h" #ifdef DEBUG #define DBG(fmt...) udbg_printf(fmt) @@ -165,6 +166,7 @@ static void __init cell_setup_arch(void) init_pci_config_tokens(); find_and_init_phbs(); spider_init_IRQ(); + cell_pervasive_init(); #ifdef CONFIG_DUMMY_CONSOLE conswitchp = &dummy_con; #endif Index: linux-2.6.15-rc/include/asm-powerpc/cputable.h =================================================================== --- linux-2.6.15-rc.orig/include/asm-powerpc/cputable.h +++ linux-2.6.15-rc/include/asm-powerpc/cputable.h @@ -105,6 +105,7 @@ extern void do_cpu_ftr_fixups(unsigned l #define CPU_FTR_LOCKLESS_TLBIE ASM_CONST(0x0000040000000000) #define CPU_FTR_MMCRA_SIHV ASM_CONST(0x0000080000000000) #define CPU_FTR_CI_LARGE_PAGE ASM_CONST(0x0000100000000000) +#define CPU_FTR_PAUSE_ZERO ASM_CONST(0x0000200000000000) #else /* ensure on 32b processors the flags are available for compiling but * don't do anything */ @@ -304,7 +305,8 @@ enum { CPU_FTR_MMCRA_SIHV, CPU_FTRS_CELL = CPU_FTR_SPLIT_ID_CACHE | CPU_FTR_USE_TB | CPU_FTR_HPTE_TABLE | CPU_FTR_PPCAS_ARCH_V2 | - CPU_FTR_ALTIVEC_COMP | CPU_FTR_MMCRA | CPU_FTR_SMT, + CPU_FTR_ALTIVEC_COMP | CPU_FTR_MMCRA | CPU_FTR_SMT | + CPU_FTR_CTRL | CPU_FTR_PAUSE_ZERO, CPU_FTRS_COMPATIBLE = CPU_FTR_SPLIT_ID_CACHE | CPU_FTR_USE_TB | CPU_FTR_HPTE_TABLE | CPU_FTR_PPCAS_ARCH_V2, #endif Index: linux-2.6.15-rc/include/asm-powerpc/reg.h =================================================================== --- linux-2.6.15-rc.orig/include/asm-powerpc/reg.h +++ linux-2.6.15-rc/include/asm-powerpc/reg.h @@ -145,6 +145,10 @@ #define SPRN_CTR 0x009 /* Count Register */ #define SPRN_CTRLF 0x088 #define SPRN_CTRLT 0x098 +#define CTRL_CT 0xc0000000 /* current thread */ +#define CTRL_CT0 0x80000000 /* thread 0 */ +#define CTRL_CT1 0x40000000 /* thread 1 */ +#define CTRL_TE 0x00c00000 /* thread enable */ #define CTRL_RUNLATCH 0x1 #define SPRN_DABR 0x3F5 /* Data Address Breakpoint Register */ #define DABR_TRANSLATION (1UL << 2) @@ -257,11 +261,11 @@ #define SPRN_HID6 0x3F9 /* BE HID 6 */ #define HID6_LB (0x0F<<12) /* Concurrent Large Page Modes */ #define HID6_DLP (1<<20) /* Disable all large page modes (4K only) */ -#define SPRN_TSCR 0x399 /* Thread switch control on BE */ -#define SPRN_TTR 0x39A /* Thread switch timeout on BE */ -#define TSCR_DEC_ENABLE 0x200000 /* Decrementer Interrupt */ -#define TSCR_EE_ENABLE 0x100000 /* External Interrupt */ -#define TSCR_EE_BOOST 0x080000 /* External Interrupt Boost */ +#define SPRN_TSC_CELL 0x399 /* Thread switch control on Cell */ +#define TSC_CELL_DEC_ENABLE_0 0x400000 /* Decrementer Interrupt */ +#define TSC_CELL_DEC_ENABLE_1 0x200000 /* Decrementer Interrupt */ +#define TSC_CELL_EE_ENABLE 0x100000 /* External Interrupt */ +#define TSC_CELL_EE_BOOST 0x080000 /* External Interrupt Boost */ #define SPRN_TSC 0x3FD /* Thread switch control on others */ #define SPRN_TST 0x3FC /* Thread switch timeout on others */ #if !defined(SPRN_IAC1) && !defined(SPRN_IAC2) @@ -375,6 +379,14 @@ #define SPRN_SPRG7 0x117 /* Special Purpose Register General 7 */ #define SPRN_SRR0 0x01A /* Save/Restore Register 0 */ #define SPRN_SRR1 0x01B /* Save/Restore Register 1 */ +#define SRR1_WAKEMASK 0x00380000 /* reason for wakeup */ +#define SRR1_WAKERESET 0x00380000 /* System reset */ +#define SRR1_WAKESYSERR 0x00300000 /* System error */ +#define SRR1_WAKEEE 0x00200000 /* External interrupt */ +#define SRR1_WAKEMT 0x00280000 /* mtctrl */ +#define SRR1_WAKEDEC 0x00180000 /* Decrementer interrupt */ +#define SRR1_WAKETHERM 0x00100000 /* Thermal management interrupt */ + #ifndef SPRN_SVR #define SPRN_SVR 0x11E /* System Version Register */ #endif Index: linux-2.6.15-rc/arch/powerpc/kernel/cputable.c =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/kernel/cputable.c +++ linux-2.6.15-rc/arch/powerpc/kernel/cputable.c @@ -273,7 +273,7 @@ struct cpu_spec cpu_specs[] = { .oprofile_model = &op_model_power4, #endif }, - { /* BE DD1.x */ + { /* Cell Broadband Engine */ .pvr_mask = 0xffff0000, .pvr_value = 0x00700000, .cpu_name = "Cell Broadband Engine", Index: linux-2.6.15-rc/arch/powerpc/kernel/traps.c =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/kernel/traps.c +++ linux-2.6.15-rc/arch/powerpc/kernel/traps.c @@ -230,8 +230,10 @@ void _exception(int signr, struct pt_reg void system_reset_exception(struct pt_regs *regs) { /* See if any machine dependent calls */ - if (ppc_md.system_reset_exception) - ppc_md.system_reset_exception(regs); + if (ppc_md.system_reset_exception) { + if (ppc_md.system_reset_exception(regs)) + return; + } die("System Reset", regs, SIGABRT); Index: linux-2.6.15-rc/arch/powerpc/platforms/pseries/ras.h =================================================================== --- /dev/null +++ linux-2.6.15-rc/arch/powerpc/platforms/pseries/ras.h @@ -0,0 +1,9 @@ +#ifndef _PSERIES_RAS_H +#define _PSERIES_RAS_H + +struct pt_regs; + +extern int pSeries_system_reset_exception(struct pt_regs *regs); +extern int pSeries_machine_check_exception(struct pt_regs *regs); + +#endif /* _PSERIES_RAS_H */ Index: linux-2.6.15-rc/arch/powerpc/platforms/pseries/setup.c =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/platforms/pseries/setup.c +++ linux-2.6.15-rc/arch/powerpc/platforms/pseries/setup.c @@ -69,6 +69,7 @@ #include #include "plpar_wrappers.h" +#include "ras.h" #ifdef DEBUG #define DBG(fmt...) udbg_printf(fmt) @@ -80,9 +81,6 @@ extern void find_udbg_vterm(void); int fwnmi_active; /* TRUE if an FWNMI handler is present */ -extern void pSeries_system_reset_exception(struct pt_regs *regs); -extern int pSeries_machine_check_exception(struct pt_regs *regs); - static void pseries_shared_idle(void); static void pseries_dedicated_idle(void); Index: linux-2.6.15-rc/arch/powerpc/platforms/pseries/ras.c =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/platforms/pseries/ras.c +++ linux-2.6.15-rc/arch/powerpc/platforms/pseries/ras.c @@ -51,6 +51,8 @@ #include #include +#include "ras.h" + static unsigned char ras_log_buf[RTAS_ERROR_LOG_MAX]; static DEFINE_SPINLOCK(ras_log_buf_lock); @@ -278,7 +280,7 @@ static void fwnmi_release_errinfo(void) printk("FWNMI: nmi-interlock failed: %d\n", ret); } -void pSeries_system_reset_exception(struct pt_regs *regs) +int pSeries_system_reset_exception(struct pt_regs *regs) { if (fwnmi_active) { struct rtas_error_log *errhdr = fwnmi_get_errinfo(regs); @@ -287,6 +289,7 @@ void pSeries_system_reset_exception(stru } fwnmi_release_errinfo(); } + return 0; /* need to perform reset */ } /* Index: linux-2.6.15-rc/include/asm-powerpc/machdep.h =================================================================== --- linux-2.6.15-rc.orig/include/asm-powerpc/machdep.h +++ linux-2.6.15-rc/include/asm-powerpc/machdep.h @@ -134,7 +134,7 @@ struct machdep_calls { void (*nvram_sync)(void); /* Exception handlers */ - void (*system_reset_exception)(struct pt_regs *regs); + int (*system_reset_exception)(struct pt_regs *regs); int (*machine_check_exception)(struct pt_regs *regs); /* Motherboard/chipset features. This is a kind of general purpose From arnd at arndb.de Thu Jan 5 06:31:24 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:24 +0100 Subject: [PATCH 04/13] spufs: serialize sys_spu_run per spu References: <20060104193120.050539000@localhost> Message-ID: <20060104194500.696404000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-serialize-spu-run.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/33e07b1e/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:22 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:22 +0100 Subject: [PATCH 02/13] spufs: dont hold root->isem in spu_forget References: <20060104193120.050539000@localhost> Message-ID: <20060104194500.352612000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-final-iput.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/9d59f488/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:21 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:21 +0100 Subject: [PATCH 01/13] spufs: fix locking in spu_acquire_runnable References: <20060104193120.050539000@localhost> Message-ID: <20060104194500.180477000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-lock.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/9c37eded/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:28 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:28 +0100 Subject: [PATCH 08/13] spufs: clean up use of bitops References: <20060104193120.050539000@localhost> Message-ID: <20060104194501.381895000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-test-bit-cleanup.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/c6c77042/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:27 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:27 +0100 Subject: [PATCH 07/13] spufs: fix spufs_fill_dir error path References: <20060104193120.050539000@localhost> Message-ID: <20060104194501.210484000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-fill-dir-leak.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/18b6b5d0/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:26 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:26 +0100 Subject: [PATCH 06/13] spufs: dont leak directories in failed spu_create References: <20060104193120.050539000@localhost> Message-ID: <20060104194501.041011000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-create-fix-leak.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/251ae601/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:30 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:30 +0100 Subject: [PATCH 10/13] spufs: abstract priv1 register access. References: <20060104193120.050539000@localhost> Message-ID: <20060104194501.737741000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-priv1-hvcall.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/6115452f/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:23 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:23 +0100 Subject: [PATCH 03/13] spufs: check for proper file pointer in sys_spu_run References: <20060104193120.050539000@localhost> Message-ID: <20060104194500.522025000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-run-check-fd.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/cbc9a713/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:29 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:29 +0100 Subject: [PATCH 09/13] spufs: move spu_run call to its own file References: <20060104193120.050539000@localhost> Message-ID: <20060104194501.555900000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-run-c-2.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/4dbafa57/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:25 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:25 +0100 Subject: [PATCH 05/13] spufs fix spu_acquire_runnable error path References: <20060104193120.050539000@localhost> Message-ID: <20060104194500.870361000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-aquire-runnable-fix.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/adb56348/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:32 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:32 +0100 Subject: [PATCH 12/13] spufs: fix allocation on 64k pages References: <20060104193120.050539000@localhost> Message-ID: <20060104194502.080544000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-64k-page.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/6557d08c/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:33 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:33 +0100 Subject: [PATCH 13/13] spufs: set irq affinity for running threads References: <20060104193120.050539000@localhost> Message-ID: <20060104194502.253418000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spu-irq-affinity.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/5d14f103/attachment.txt From arnd at arndb.de Thu Jan 5 06:31:20 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:20 +0100 Subject: [PATCH 00/13] spufs fixes and cleanups Message-ID: <20060104193120.050539000@localhost> In a review almost a month ago, Al Viro found numerous problems in the current spufs code. I now finally found some time to go through those and attempt to fix them. There are also a few other changes in this series that should also help, in particular improved interrupt sending and an abstraction for priviledged register access (as suggested by Masato Noguchi and Geoff Levand). Please apply to powerpc.git before sending spufs upstream. Al, could you have a look over this to see if it addresses all the concerns you had and if I broke it in new ways? Arnd <>< arch/powerpc/platforms/cell/Makefile | 5 arch/powerpc/platforms/cell/interrupt.c | 42 ++-- arch/powerpc/platforms/cell/interrupt.h | 1 arch/powerpc/platforms/cell/spu_base.c | 67 +++---- arch/powerpc/platforms/cell/spu_priv1.c | 133 ++++++++++++++ arch/powerpc/platforms/cell/spufs/Makefile | 2 arch/powerpc/platforms/cell/spufs/file.c | 167 +---------------- arch/powerpc/platforms/cell/spufs/hw_ops.c | 19 -- arch/powerpc/platforms/cell/spufs/inode.c | 156 ++++++++------- arch/powerpc/platforms/cell/spufs/run.c | 131 ++++++++++++++ arch/powerpc/platforms/cell/spufs/sched.c | 13 + arch/powerpc/platforms/cell/spufs/spufs.h | 35 +++ arch/powerpc/platforms/cell/spufs/switch.c | 139 +++++---------- arch/powerpc/platforms/cell/spufs/syscalls.c | 5 arch/powerpc/platforms/cell/spufs/context.c | 11 - include/asm-powerpc/spu.h | 42 +++- 20 files changed, 565 insertions(+), 407 deletions(-) From arnd at arndb.de Thu Jan 5 06:31:31 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Wed, 04 Jan 2006 20:31:31 +0100 Subject: [PATCH 11/13] spufs: fix sparse warnings References: <20060104193120.050539000@localhost> Message-ID: <20060104194501.915556000@localhost> An embedded and charset-unspecified text was scrubbed... Name: spufs-sparse-fixes.diff Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060104/fcedcd1f/attachment.txt From benh at kernel.crashing.org Thu Jan 5 10:41:29 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Thu, 05 Jan 2006 10:41:29 +1100 Subject: Phantom pain with windfarm on diskless iMac G5 In-Reply-To: <20060104142006.GA8782@victor.cl.uni-heidelberg.de> References: <20060104142006.GA8782@victor.cl.uni-heidelberg.de> Message-ID: <1136418090.4840.21.camel@localhost.localdomain> On Wed, 2006-01-04 at 15:20 +0100, Markus Demleitner wrote: > Hi, > > I tried 2.6.15 on my diskless iMac G5 clients today, resulting in > 747 emulation mode (vrooom...!). It turns out windfarm was > querying the hard disk temperature sensor, which usually is mounted > on the mounting bracket Apple uses. We made the mistake of removing > these (from about 30 machines:-(), which in turn made > windfarm_lm75_sensor.c:wf_lm75_get return ffff, which translates into > about 255 degrees celsius. No wonder windfarm pumped like there's no > tomorrow. > > I've "fixed" this by returning some fixed low temperature if I see > ffff in wf_lm75_get for now, but I *guess* it would be nice to have > some way to detect the absence of the sensor (and tell it from a > simple failure). However, the OF device trees still list the sensor > and even the hard disk itself even on the diskless machines. The problem is to differenciate between a diskless machine and a defective sensor. In the later case, you _want_ to pump the fans. > Even if there were a way to detect the absence of the sensor, there's > still the problem that windfarm_pm81.c insists on having a hd temp > sensor to work, so a fix would probably require spoiling that > wonderful > if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp) > in there and replacing it with something like > if (sensor_cpu_power && sensor_cpu_temp && (machine_has_hd() > && sensor_hd_temp)) > where I have no idea how to implement machine_has_hd(). A further > similar hack would spoil wf_smu_sys_fans_tick, and uglyness prevails. > > In short: Am I doomed to hack the kernels of my diskless clients to > eternity (or retrofit the sensors)? Or is there a sane way to treat > that kind of problem? Hrm... That isn't trivial as I don't see a clean way to detect that the HD is not there from windfarm without doing gross hacks, unless we can somewhat rely on the device-tree there... What we could do is: - Make pm81 start the control loops regardless of the presence of the sensor, and have the control loop itself set the disk fan to an arbitrary low value if the sensor is not there. If the sensor kicks in "later" (because lm75 loads later), it will automatically start using the full control loop. That is easy. - In lm75 itself, in case of failure, add a little hack that tests if the disk is present by looking in the device-tree, provided again that there is a node for it that can be detected... If not, then return an arbitrarily low temperature instead of a failure. Either that or a module/kernel command line option... The later is easier but less "neat" :) Ben. From ntl at pobox.com Thu Jan 5 15:42:27 2006 From: ntl at pobox.com (Nathan Lynch) Date: Wed, 4 Jan 2006 22:42:27 -0600 Subject: [PATCH 13/13] spufs: set irq affinity for running threads In-Reply-To: <20060104194502.253418000@localhost> References: <20060104193120.050539000@localhost> <20060104194502.253418000@localhost> Message-ID: <20060105044227.GD16729@localhost.localdomain> Arnd Bergmann wrote: > For far, all SPU triggered interrupts always end up on > the first SMT thread, which is a bad solution. > > This patch implements setting the affinity to the > CPU that was running last when entering execution on > an SPU. This should result in a significant reduction > in IPI calls and better cache locality for SPE thread > specific data. ... > --- linux-2.6.15-rc.orig/arch/powerpc/platforms/cell/spufs/sched.c > +++ linux-2.6.15-rc/arch/powerpc/platforms/cell/spufs/sched.c > @@ -357,6 +357,11 @@ int spu_activate(struct spu_context *ctx > if (!spu) > return (signal_pending(current)) ? -ERESTARTSYS : -EAGAIN; > bind_context(spu, ctx); > + /* > + * We're likely to wait for interrupts on the same > + * CPU that we are now on, so send them here. > + */ > + spu_irq_setaffinity(spu, smp_processor_id()); With CONFIG_DEBUG_PREEMPT this will give a warning about using smp_processor_id in pre-emptible context if I'm reading the code correctly. Maybe use raw_smp_processor_id, since setting the affinity to this cpu isn't a hard requirement? From benh at kernel.crashing.org Thu Jan 5 16:39:44 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Thu, 05 Jan 2006 16:39:44 +1100 Subject: [PATCH] powerpc: Add PowerMac platform function interpreter Message-ID: <1136439584.4840.48.camel@localhost.localdomain> (Preliminary, still need a little bit of work, but I would greatly appreciate some regression testing with ARCH=powerpc, this code doesn't affect ARCH=ppc, especially on any laptop or desktop released in the past 3 or 4 years). This patch adds an interpreter for the PowerMac "platform-do-*" scripts found in the device-tree, along with some of the backends to be able to execute the primitives in there. For now, the backends for uninorth/u3, the macio chip, the gpios and some devices hanging off keywest i2c. In order to do so, I had to significantly rework the PowerMac "low_i2c" layer, this work isn't complete yet as you can see. Ultimately, it will be able to help in matching device nodes to i2c interfaces. I also might kill the existing i2c-keywest and i2c-pmac-smu drivers and just do a single "stub" driver that layers on top of the low i2c. Right now, it only provides keywest platform functions of the i2c hwclock, some more work will be needed to properly do some of the manipulations needed with the hwclock hanging off the PMU bus, on some windtunnel machines for example. Currently, the code for matching with i2c_adpater isn't working as none of the "high level" drivers is calling the necessary hooks, I'll fix that in a future version of that patch. I removed the clock spreading hacks in feature.c too, since they should now be entirely done by the platform functions, but I couldn't test properly as I don't have access to one of the laptops that has those... It's a bit difficult to "veryfiy" if it works now that bogomips are gone on powerpc, since the consequence of it not working is the CPU running about 20% slower than it should, thus I'll have to write a small test program that attempts to "measure" the processor frequency... I also changed the SMP code for newer G5s (multi core) to use the platform function when available for the timebase freeze, that appears to work properly on my Quad. Anyway, here it is, comments welcome... Signed-off-by: Benjamin Herrenschmidt Index: linux-work/include/asm-powerpc/pmac_pfunc.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-work/include/asm-powerpc/pmac_pfunc.h 2006-01-04 18:35:10.000000000 +1100 @@ -0,0 +1,253 @@ +#ifndef __PMAC_PFUNC_H__ +#define __PMAC_PFUNC_H__ + +#include +#include + +/* Flags in command lists */ +#define PMF_FLAGS_ON_INIT 0x80000000u +#define PMF_FLGAS_ON_TERM 0x40000000u +#define PMF_FLAGS_ON_SLEEP 0x20000000u +#define PMF_FLAGS_ON_WAKE 0x10000000u +#define PMF_FLAGS_ON_DEMAND 0x08000000u +#define PMF_FLAGS_INT_GEN 0x04000000u +#define PMF_FLAGS_HIGH_SPEED 0x02000000u +#define PMF_FLAGS_LOW_SPEED 0x01000000u +#define PMF_FLAGS_SIDE_EFFECTS 0x00800000u + +/* + * Arguments to a platform function call. + * + * NOTE: By convention, pointer arguments point to an u32 + */ +struct pmf_args { + union { + u32 v; + u32 *p; + } u[4]; + unsigned int count; +}; + +/* + * A driver capable of interpreting commands provides a handlers + * structure filled with whatever handlers are implemented by this + * driver. Non implemented handlers are left NULL. + * + * PMF_STD_ARGS are the same arguments that are passed to the parser + * and that gets passed back to the various handlers. + * + * Interpreting a given function always start with a begin() call which + * returns an instance data to be passed around subsequent calls, and + * ends with an end() call. This allows the low level driver to implement + * locking policy or per-function instance data. + * + * For interrupt capable functions, irq_enable() is called when a client + * registers, and irq_disable() is called when the last client unregisters + * Note that irq_enable & irq_disable are called within a semaphore held + * by the core, thus you should not try to register yourself to some other + * pmf interrupt during those calls. + */ + +#define PMF_STD_ARGS struct pmf_function *func, void *instdata, \ + struct pmf_args *args + +struct pmf_function; + +struct pmf_handlers { + void * (*begin)(struct pmf_function *func, struct pmf_args *args); + void (*end)(struct pmf_function *func, void *instdata); + + int (*irq_enable)(struct pmf_function *func); + int (*irq_disable)(struct pmf_function *func); + + int (*write_gpio)(PMF_STD_ARGS, u8 value, u8 mask); + int (*read_gpio)(PMF_STD_ARGS, u8 mask, int rshift, u8 xor); + + int (*write_reg32)(PMF_STD_ARGS, u32 offset, u32 value, u32 mask); + int (*read_reg32)(PMF_STD_ARGS, u32 offset); + int (*write_reg16)(PMF_STD_ARGS, u32 offset, u16 value, u16 mask); + int (*read_reg16)(PMF_STD_ARGS, u32 offset); + int (*write_reg8)(PMF_STD_ARGS, u32 offset, u8 value, u8 mask); + int (*read_reg8)(PMF_STD_ARGS, u32 offset); + + int (*delay)(PMF_STD_ARGS, u32 duration); + + int (*wait_reg32)(PMF_STD_ARGS, u32 offset, u32 value, u32 mask); + int (*wait_reg16)(PMF_STD_ARGS, u32 offset, u16 value, u16 mask); + int (*wait_reg8)(PMF_STD_ARGS, u32 offset, u8 value, u8 mask); + + int (*read_i2c)(PMF_STD_ARGS, u32 len); + int (*write_i2c)(PMF_STD_ARGS, u32 len, const u8 *data); + int (*rmw_i2c)(PMF_STD_ARGS, u32 masklen, u32 valuelen, u32 totallen, + const u8 *maskdata, const u8 *valuedata); + + int (*read_cfg)(PMF_STD_ARGS, u32 offset, u32 len); + int (*write_cfg)(PMF_STD_ARGS, u32 offset, u32 len, const u8 *data); + int (*rmw_cfg)(PMF_STD_ARGS, u32 offset, u32 masklen, u32 valuelen, + u32 totallen, const u8 *maskdata, const u8 *valuedata); + + int (*read_i2c_sub)(PMF_STD_ARGS, u8 subaddr, u32 len); + int (*write_i2c_sub)(PMF_STD_ARGS, u8 subaddr, u32 len, const u8 *data); + int (*set_i2c_mode)(PMF_STD_ARGS, int mode); + int (*rmw_i2c_sub)(PMF_STD_ARGS, u8 subaddr, u32 masklen, u32 valuelen, + u32 totallen, const u8 *maskdata, + const u8 *valuedata); + + int (*read_reg32_msrx)(PMF_STD_ARGS, u32 offset, u32 mask, u32 shift, + u32 xor); + int (*read_reg16_msrx)(PMF_STD_ARGS, u32 offset, u32 mask, u32 shift, + u32 xor); + int (*read_reg8_msrx)(PMF_STD_ARGS, u32 offset, u32 mask, u32 shift, + u32 xor); + + int (*write_reg32_slm)(PMF_STD_ARGS, u32 offset, u32 shift, u32 mask); + int (*write_reg16_slm)(PMF_STD_ARGS, u32 offset, u32 shift, u32 mask); + int (*write_reg8_slm)(PMF_STD_ARGS, u32 offset, u32 shift, u32 mask); + + int (*mask_and_compare)(PMF_STD_ARGS, u32 len, const u8 *maskdata, + const u8 *valuedata); + + struct module *owner; +}; + + +/* + * Drivers who expose platform functions register at init time, this + * causes the platform functions for that device node to be parsed in + * advance and associated with the device. The data structures are + * partially public so a driver can walk the list of platform functions + * and eventually inspect the flags + */ +struct pmf_device; + +struct pmf_function { + /* All functions for a given driver are linked */ + struct list_head link; + + /* Function node & driver data */ + struct device_node *node; + void *driver_data; + + /* For internal use by core */ + struct pmf_device *dev; + + /* The name is the "xxx" in "platform-do-xxx", this is how + * platform functions are identified by this code. Some functions + * only operate for a given target, in which case the phandle is + * here (or 0 if the filter doesn't apply) + */ + const char *name; + u32 phandle; + + /* The flags for that function. You can have several functions + * with the same name and different flag + */ + u32 flags; + + /* The actual tokenized function blob */ + const void *data; + unsigned int length; + + /* Interrupt clients */ + struct list_head irq_clients; + + /* Refcounting */ + struct kref ref; +}; + +/* + * For platform functions that are interrupts, one can register + * irq_client structures. You canNOT use the same structure twice + * as it contains a link member. Also, the callback is called with + * a spinlock held, you must not call back into any of the pmf_* functions + * from within that callback + */ +struct pmf_irq_client { + void (*handler)(void *data); + void *data; + struct module *owner; + struct list_head link; +}; + + +/* + * Register/Unregister a function-capable driver and its handlers + */ +extern int pmf_register_driver(struct device_node *np, + struct pmf_handlers *handlers, + void *driverdata); + +extern void pmf_unregister_driver(struct device_node *np); + + +/* + * Register/Unregister interrupt clients + */ +extern int pmf_register_irq_client(struct device_node *np, + const char *name, + struct pmf_irq_client *client); + +extern void pmf_unregister_irq_client(struct device_node *np, + const char *name, + struct pmf_irq_client *client); + +/* + * Called by the handlers when an irq happens + */ +extern void pmf_do_irq(struct pmf_function *func); + + +/* + * Low level call to platform functions. + * + * The phandle can filter on the target object for functions that have + * multiple targets, the flags allow you to restrict the call to a given + * combination of flags. + * + * The args array contains as many arguments as is required by the function, + * this is dependent on the function you are calling, unfortunately Apple + * mecanism provides no way to encode that so you have to get it right at + * the call site. Some functions require no args, in which case, you can + * pass NULL. + * + * You can also pass NULL to the name. This will match any function that has + * the appropriate combination of flags & phandle or you can pass 0 to the + * phandle to match any + */ +extern int pmf_do_functions(struct device_node *np, const char *name, + u32 phandle, u32 flags, struct pmf_args *args); + + + +/* + * High level call to a platform function. + * + * This one looks for the platform-xxx first so you should call it to the + * actual target if any. It will fallback to platform-do-xxx if it can't + * find one. It will also exclusively target functions that have + * the "OnDemand" flag. + */ + +extern int pmf_call_function(struct device_node *target, const char *name, + struct pmf_args *args); + + +/* + * For low latency interrupt usage, you can lookup for on-demand functions + * using the functions below + */ + +extern struct pmf_function *pmf_find_function(struct device_node *target, + const char *name); + +extern struct pmf_function * pmf_get_function(struct pmf_function *func); +extern void pmf_put_function(struct pmf_function *func); + +extern int pmf_call_one(struct pmf_function *func, struct pmf_args *args); + + +/* Suspend/resume code called by via-pmu directly for now */ +extern void pmac_pfunc_base_suspend(void); +extern void pmac_pfunc_base_resume(void); + +#endif /* __PMAC_PFUNC_H__ */ Index: linux-work/arch/powerpc/platforms/powermac/Makefile =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/Makefile 2006-01-04 11:55:10.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/Makefile 2006-01-04 11:55:25.000000000 +1100 @@ -1,7 +1,8 @@ CFLAGS_bootx_init.o += -fPIC obj-y += pic.o setup.o time.o feature.o pci.o \ - sleep.o low_i2c.o cache.o + sleep.o low_i2c.o cache.o pfunc_core.o \ + pfunc_base.o obj-$(CONFIG_PMAC_BACKLIGHT) += backlight.o obj-$(CONFIG_CPU_FREQ_PMAC) += cpufreq_32.o obj-$(CONFIG_CPU_FREQ_PMAC64) += cpufreq_64.o Index: linux-work/arch/powerpc/platforms/powermac/feature.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/feature.c 2006-01-04 11:55:10.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/feature.c 2006-01-04 16:36:11.000000000 +1100 @@ -58,12 +58,11 @@ extern int powersave_nap; extern struct device_node *k2_skiplist[2]; - /* * We use a single global lock to protect accesses. Each driver has * to take care of its own locking */ -static DEFINE_SPINLOCK(feature_lock); +DEFINE_SPINLOCK(feature_lock); #define LOCK(flags) spin_lock_irqsave(&feature_lock, flags); #define UNLOCK(flags) spin_unlock_irqrestore(&feature_lock, flags); @@ -106,22 +105,12 @@ }; +struct device_node *uninorth_node; +u32 __iomem *uninorth_base; -/* - * Uninorth reg. access. Note that Uni-N regs are big endian - */ - -#define UN_REG(r) (uninorth_base + ((r) >> 2)) -#define UN_IN(r) (in_be32(UN_REG(r))) -#define UN_OUT(r,v) (out_be32(UN_REG(r), (v))) -#define UN_BIS(r,v) (UN_OUT((r), UN_IN(r) | (v))) -#define UN_BIC(r,v) (UN_OUT((r), UN_IN(r) & ~(v))) - -static struct device_node *uninorth_node; -static u32 __iomem *uninorth_base; static u32 uninorth_rev; static int uninorth_maj; -static void __iomem *u3_ht; +static void __iomem *u3_ht_base; /* * For each motherboard family, we have a table of functions pointers @@ -1560,8 +1549,10 @@ #ifndef CONFIG_POWER4 -static void -keylargo_shutdown(struct macio_chip *macio, int sleep_mode) + +#ifdef CONFIG_PM + +static void keylargo_shutdown(struct macio_chip *macio, int sleep_mode) { u32 temp; @@ -1614,8 +1605,7 @@ (void)MACIO_IN32(KEYLARGO_FCR0); mdelay(1); } -static void -pangea_shutdown(struct macio_chip *macio, int sleep_mode) +static void pangea_shutdown(struct macio_chip *macio, int sleep_mode) { u32 temp; @@ -1648,8 +1638,7 @@ (void)MACIO_IN32(KEYLARGO_FCR0); mdelay(1); } -static void -intrepid_shutdown(struct macio_chip *macio, int sleep_mode) +static void intrepid_shutdown(struct macio_chip *macio, int sleep_mode) { u32 temp; @@ -1676,125 +1665,6 @@ mdelay(10); } - -void pmac_tweak_clock_spreading(int enable) -{ - struct macio_chip *macio = &macio_chips[0]; - - /* Hack for doing clock spreading on some machines PowerBooks and - * iBooks. This implements the "platform-do-clockspreading" OF - * property as decoded manually on various models. For safety, we also - * check the product ID in the device-tree in cases we'll whack the i2c - * chip to make reasonably sure we won't set wrong values in there - * - * Of course, ultimately, we have to implement a real parser for - * the platform-do-* stuff... - */ - - if (macio->type == macio_intrepid) { - struct device_node *clock = - of_find_node_by_path("/uni-n at f8000000/hw-clock"); - if (clock && get_property(clock, "platform-do-clockspreading", - NULL)) { - printk(KERN_INFO "%sabling clock spreading on Intrepid" - " ASIC\n", enable ? "En" : "Dis"); - if (enable) - UN_OUT(UNI_N_CLOCK_SPREADING, 2); - else - UN_OUT(UNI_N_CLOCK_SPREADING, 0); - mdelay(40); - } - of_node_put(clock); - } - - while (machine_is_compatible("PowerBook5,2") || - machine_is_compatible("PowerBook5,3") || - machine_is_compatible("PowerBook6,2") || - machine_is_compatible("PowerBook6,3")) { - struct device_node *ui2c = of_find_node_by_type(NULL, "i2c"); - struct device_node *dt = of_find_node_by_name(NULL, "device-tree"); - u8 buffer[9]; - u32 *productID; - int i, rc, changed = 0; - - if (dt == NULL) - break; - productID = (u32 *)get_property(dt, "pid#", NULL); - if (productID == NULL) - break; - while(ui2c) { - struct device_node *p = of_get_parent(ui2c); - if (p && !strcmp(p->name, "uni-n")) - break; - ui2c = of_find_node_by_type(ui2c, "i2c"); - } - if (ui2c == NULL) - break; - DBG("Trying to bump clock speed for PID: %08x...\n", *productID); - rc = pmac_low_i2c_open(ui2c, 1); - if (rc != 0) - break; - pmac_low_i2c_setmode(ui2c, pmac_low_i2c_mode_combined); - rc = pmac_low_i2c_xfer(ui2c, 0xd2 | pmac_low_i2c_read, 0x80, buffer, 9); - DBG("read result: %d,", rc); - if (rc != 0) { - pmac_low_i2c_close(ui2c); - break; - } - for (i=0; i<9; i++) - DBG(" %02x", buffer[i]); - DBG("\n"); - - switch(*productID) { - case 0x1182: /* AlBook 12" rev 2 */ - case 0x1183: /* iBook G4 12" */ - buffer[0] = (buffer[0] & 0x8f) | 0x70; - buffer[2] = (buffer[2] & 0x7f) | 0x00; - buffer[5] = (buffer[5] & 0x80) | 0x31; - buffer[6] = (buffer[6] & 0x40) | 0xb0; - buffer[7] = (buffer[7] & 0x00) | (enable ? 0xc0 : 0xba); - buffer[8] = (buffer[8] & 0x00) | 0x30; - changed = 1; - break; - case 0x3142: /* AlBook 15" (ATI M10) */ - case 0x3143: /* AlBook 17" (ATI M10) */ - buffer[0] = (buffer[0] & 0xaf) | 0x50; - buffer[2] = (buffer[2] & 0x7f) | 0x00; - buffer[5] = (buffer[5] & 0x80) | 0x31; - buffer[6] = (buffer[6] & 0x40) | 0xb0; - buffer[7] = (buffer[7] & 0x00) | (enable ? 0xd0 : 0xc0); - buffer[8] = (buffer[8] & 0x00) | 0x30; - changed = 1; - break; - default: - DBG("i2c-hwclock: Machine model not handled\n"); - break; - } - if (!changed) { - pmac_low_i2c_close(ui2c); - break; - } - printk(KERN_INFO "%sabling clock spreading on i2c clock chip\n", - enable ? "En" : "Dis"); - - pmac_low_i2c_setmode(ui2c, pmac_low_i2c_mode_stdsub); - rc = pmac_low_i2c_xfer(ui2c, 0xd2 | pmac_low_i2c_write, 0x80, buffer, 9); - DBG("write result: %d,", rc); - pmac_low_i2c_setmode(ui2c, pmac_low_i2c_mode_combined); - rc = pmac_low_i2c_xfer(ui2c, 0xd2 | pmac_low_i2c_read, 0x80, buffer, 9); - DBG("read result: %d,", rc); - if (rc != 0) { - pmac_low_i2c_close(ui2c); - break; - } - for (i=0; i<9; i++) - DBG(" %02x", buffer[i]); - pmac_low_i2c_close(ui2c); - break; - } -} - - static int core99_sleep(void) { @@ -1951,6 +1821,8 @@ return 0; } +#endif /* CONFIG_PM */ + static long core99_sleep_state(struct device_node *node, long param, long value) { @@ -1972,10 +1844,13 @@ if ((pmac_mb.board_flags & PMAC_MB_CAN_SLEEP) == 0) return -EPERM; +#ifdef CONFIG_PM if (value == 1) return core99_sleep(); else if (value == 0) return core99_wake_up(); + +#endif /* CONFIG_PM */ return 0; } @@ -2099,7 +1974,9 @@ { PMAC_FTR_USB_ENABLE, core99_usb_enable }, { PMAC_FTR_1394_ENABLE, core99_firewire_enable }, { PMAC_FTR_1394_CABLE_POWER, core99_firewire_cable_power }, +#ifdef CONFIG_PM { PMAC_FTR_SLEEP_STATE, core99_sleep_state }, +#endif #ifdef CONFIG_SMP { PMAC_FTR_RESET_CPU, core99_reset_cpu }, #endif /* CONFIG_SMP */ @@ -2690,7 +2567,7 @@ uninorth_base = ioremap(address, 0x40000); uninorth_rev = in_be32(UN_REG(UNI_N_VERSION)); if (uninorth_maj == 3 || uninorth_maj == 4) - u3_ht = ioremap(address + U3_HT_CONFIG_BASE, 0x1000); + u3_ht_base = ioremap(address + U3_HT_CONFIG_BASE, 0x1000); printk(KERN_INFO "Found %s memory controller & host bridge" " @ 0x%08x revision: 0x%02x\n", uninorth_maj == 3 ? "U3" : @@ -2980,12 +2857,6 @@ MACIO_BIC(HEATHROW_FCR, HRW_SOUND_POWER_N); } - /* Some machine models need the clock chip to be properly setup for - * clock spreading now. This should be a platform function but we - * don't do these at the moment - */ - pmac_tweak_clock_spreading(1); - #endif /* CONFIG_POWER4 */ /* On all machines, switch modem & serial ports off */ @@ -3013,9 +2884,6 @@ return; } - /* Setup low-level i2c stuffs */ - pmac_init_low_i2c(); - /* Probe machine type */ if (probe_motherboard()) printk(KERN_WARNING "Unknown PowerMac !\n"); @@ -3048,9 +2916,9 @@ u8 px_bus, px_devfn; struct pci_controller *px_hose; - (void)in_be32(u3_ht + U3_HT_LINK_COMMAND); - ucfg = cfg = in_be32(u3_ht + U3_HT_LINK_CONFIG); - ufreq = freq = in_be32(u3_ht + U3_HT_LINK_FREQ); + (void)in_be32(u3_ht_base + U3_HT_LINK_COMMAND); + ucfg = cfg = in_be32(u3_ht_base + U3_HT_LINK_CONFIG); + ufreq = freq = in_be32(u3_ht_base + U3_HT_LINK_FREQ); dump_HT_speeds("U3 HyperTransport", cfg, freq); pcix_node = of_find_compatible_node(NULL, "pci", "pci-x"); Index: linux-work/arch/powerpc/platforms/powermac/pfunc_base.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-work/arch/powerpc/platforms/powermac/pfunc_base.c 2006-01-04 17:51:17.000000000 +1100 @@ -0,0 +1,405 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DBG(fmt...) printk(fmt) + +static irqreturn_t macio_gpio_irq(int irq, void *data, struct pt_regs *regs) +{ + pmf_do_irq(data); + + return IRQ_HANDLED; +} + +static int macio_do_gpio_irq_enable(struct pmf_function *func) +{ + if (func->node->n_intrs < 1) + return -EINVAL; + + return request_irq(func->node->intrs[0].line, macio_gpio_irq, 0, + func->node->name, func); +} + +static int macio_do_gpio_irq_disable(struct pmf_function *func) +{ + if (func->node->n_intrs < 1) + return -EINVAL; + + free_irq(func->node->intrs[0].line, func); + return 0; +} + +static int macio_do_gpio_write(PMF_STD_ARGS, u8 value, u8 mask) +{ + u8 __iomem *addr = (u8 __iomem *)func->driver_data; + unsigned long flags; + u8 tmp; + + /* Check polarity */ + if (args && args->count && !args->u[0].v) + value = ~value; + + /* Toggle the GPIO */ + spin_lock_irqsave(&feature_lock, flags); + tmp = readb(addr); + tmp = (tmp & ~mask) | (value & mask); + DBG("Do write 0x%02x to GPIO %s (%p)\n", + tmp, func->node->full_name, addr); + writeb(tmp, addr); + spin_unlock_irqrestore(&feature_lock, flags); + + return 0; +} + +static int macio_do_gpio_read(PMF_STD_ARGS, u8 mask, int rshift, u8 xor) +{ + u8 __iomem *addr = (u8 __iomem *)func->driver_data; + u32 value; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + value = readb(addr); + *args->u[0].p = ((value & mask) >> rshift) ^ xor; + + return 0; +} + +static int macio_do_delay(PMF_STD_ARGS, u32 duration) +{ + /* assume we can sleep ! */ + msleep((duration + 999) / 1000); + return 0; +} + +static struct pmf_handlers macio_gpio_handlers = { + .irq_enable = macio_do_gpio_irq_enable, + .irq_disable = macio_do_gpio_irq_disable, + .write_gpio = macio_do_gpio_write, + .read_gpio = macio_do_gpio_read, + .delay = macio_do_delay, +}; + +static void macio_gpio_init_one(struct macio_chip *macio) +{ + struct device_node *gparent, *gp; + + /* + * Find the "gpio" parent node + */ + + for (gparent = NULL; + (gparent = of_get_next_child(macio->of_node, gparent)) != NULL;) + if (strcmp(gparent->name, "gpio") == 0) + break; + if (gparent == NULL) + return; + + DBG("Installing GPIO functions for macio %s\n", + macio->of_node->full_name); + + /* + * Ok, got one, we dont need anything special to track them down, so + * we just create them all + */ + for (gp = NULL; (gp = of_get_next_child(gparent, gp)) != NULL;) { + u32 *reg = (u32 *)get_property(gp, "reg", NULL); + unsigned long offset; + if (reg == NULL) + continue; + offset = *reg; + /* Deal with old style device-tree. We can safely hard code the + * offset for now too even if it's a bit gross ... + */ + if (offset < 0x50) + offset += 0x50; + offset += (unsigned long)macio->base; + pmf_register_driver(gp, &macio_gpio_handlers, (void *)offset); + } + + DBG("Calling initial GPIO functions for macio %s\n", + macio->of_node->full_name); + + /* And now we run all the init ones */ + for (gp = NULL; (gp = of_get_next_child(gparent, gp)) != NULL;) + pmf_do_functions(gp, NULL, 0, PMF_FLAGS_ON_INIT, NULL); + + /* Note: We do not at this point implement the "at sleep" or "at wake" + * functions. I yet to find any for GPIOs anyway + */ +} + +static int macio_do_write_reg32(PMF_STD_ARGS, u32 offset, u32 value, u32 mask) +{ + struct macio_chip *macio = func->driver_data; + unsigned long flags; + + spin_lock_irqsave(&feature_lock, flags); + MACIO_OUT32(offset, (MACIO_IN32(offset) & ~mask) | (value & mask)); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + +static int macio_do_read_reg32(PMF_STD_ARGS, u32 offset) +{ + struct macio_chip *macio = func->driver_data; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + *args->u[0].p = MACIO_IN32(offset); + return 0; +} + +static int macio_do_write_reg8(PMF_STD_ARGS, u32 offset, u8 value, u8 mask) +{ + struct macio_chip *macio = func->driver_data; + unsigned long flags; + + spin_lock_irqsave(&feature_lock, flags); + MACIO_OUT8(offset, (MACIO_IN8(offset) & ~mask) | (value & mask)); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + +static int macio_do_read_reg8(PMF_STD_ARGS, u32 offset) +{ + struct macio_chip *macio = func->driver_data; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + *((u8 *)(args->u[0].p)) = MACIO_IN8(offset); + return 0; +} + +static int macio_do_read_reg32_msrx(PMF_STD_ARGS, u32 offset, u32 mask, + u32 shift, u32 xor) +{ + struct macio_chip *macio = func->driver_data; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + *args->u[0].p = ((MACIO_IN32(offset) & mask) >> shift) ^ xor; + return 0; +} + +static int macio_do_read_reg8_msrx(PMF_STD_ARGS, u32 offset, u32 mask, + u32 shift, u32 xor) +{ + struct macio_chip *macio = func->driver_data; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + *((u8 *)(args->u[0].p)) = ((MACIO_IN8(offset) & mask) >> shift) ^ xor; + return 0; +} + +static int macio_do_write_reg32_slm(PMF_STD_ARGS, u32 offset, u32 shift, + u32 mask) +{ + struct macio_chip *macio = func->driver_data; + unsigned long flags; + u32 tmp, val; + + /* Check args */ + if (args == NULL || args->count == 0) + return -EINVAL; + + spin_lock_irqsave(&feature_lock, flags); + tmp = MACIO_IN32(offset); + val = args->u[0].v << shift; + tmp = (tmp & ~mask) | (val & mask); + MACIO_OUT32(offset, tmp); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + +static int macio_do_write_reg8_slm(PMF_STD_ARGS, u32 offset, u32 shift, + u32 mask) +{ + struct macio_chip *macio = func->driver_data; + unsigned long flags; + u32 tmp, val; + + /* Check args */ + if (args == NULL || args->count == 0) + return -EINVAL; + + spin_lock_irqsave(&feature_lock, flags); + tmp = MACIO_IN8(offset); + val = args->u[0].v << shift; + tmp = (tmp & ~mask) | (val & mask); + MACIO_OUT8(offset, tmp); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + +static struct pmf_handlers macio_mmio_handlers = { + .write_reg32 = macio_do_write_reg32, + .read_reg32 = macio_do_read_reg32, + .write_reg8 = macio_do_write_reg8, + .read_reg32 = macio_do_read_reg8, + .read_reg32_msrx = macio_do_read_reg32_msrx, + .read_reg8_msrx = macio_do_read_reg8_msrx, + .write_reg32_slm = macio_do_write_reg32_slm, + .write_reg8_slm = macio_do_write_reg8_slm, + .delay = macio_do_delay, +}; + +static void macio_mmio_init_one(struct macio_chip *macio) +{ + DBG("Installing MMIO functions for macio %s\n", + macio->of_node->full_name); + + pmf_register_driver(macio->of_node, &macio_mmio_handlers, macio); +} + +static struct device_node *unin_hwclock; + +static int unin_do_write_reg32(PMF_STD_ARGS, u32 offset, u32 value, u32 mask) +{ + unsigned long flags; + + spin_lock_irqsave(&feature_lock, flags); + /* This is fairly bogus in darwin, but it should work for our needs + * implemeted that way: + */ + UN_OUT(offset, (UN_IN(offset) & ~mask) | (value & mask)); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + + +static struct pmf_handlers unin_mmio_handlers = { + .write_reg32 = unin_do_write_reg32, + .delay = macio_do_delay, +}; + +static void uninorth_install_pfunc(void) +{ + struct device_node *np; + + DBG("Installing functions for UniN %s\n", + uninorth_node->full_name); + + /* + * Install handlers for the bridge itself + */ + pmf_register_driver(uninorth_node, &unin_mmio_handlers, NULL); + pmf_do_functions(uninorth_node, NULL, 0, PMF_FLAGS_ON_INIT, NULL); + + + /* + * Install handlers for the hwclock child if any + */ + for (np = NULL; (np = of_get_next_child(uninorth_node, np)) != NULL;) + if (strcmp(np->name, "hw-clock") == 0) { + unin_hwclock = np; + break; + } + if (unin_hwclock) { + DBG("Installing functions for UniN clock %s\n", + unin_hwclock->full_name); + pmf_register_driver(unin_hwclock, &unin_mmio_handlers, NULL); + pmf_do_functions(unin_hwclock, NULL, 0, PMF_FLAGS_ON_INIT, + NULL); + } +} + +/* We export this as the SMP code might init us early */ +int __init pmac_pfunc_base_install(void) +{ + static int pfbase_inited; + int i; + + if (pfbase_inited) + return 0; + pfbase_inited = 1; + + + DBG("Installing base platform functions...\n"); + + /* + * Locate mac-io chips and install handlers + */ + for (i = 0 ; i < MAX_MACIO_CHIPS; i++) { + if (macio_chips[i].of_node) { + macio_mmio_init_one(&macio_chips[i]); + macio_gpio_init_one(&macio_chips[i]); + } + } + + /* + * Install handlers for northbridge and direct mapped hwclock + * if any. We do not implement the config space access callback + * which is only ever used for functions that we do not call in + * the current driver (enabling/disabling cells in U2, mostly used + * to restore the PCI settings, we do that differently) + */ + if (uninorth_node && uninorth_base) + uninorth_install_pfunc(); + + DBG("All base functions installed\n"); + + return 0; +} + +arch_initcall(pmac_pfunc_base_install); + +#ifdef CONFIG_PM + +/* Those can be called by pmac_feature. Ultimately, I should use a sysdev + * or a device, but for now, that's good enough until I sort out some + * ordering issues. Also, we do not bother with GPIOs, as so far I yet have + * to see a case where a GPIO function has the on-suspend or on-resume bit + */ +void pmac_pfunc_base_suspend(void) +{ + int i; + + for (i = 0 ; i < MAX_MACIO_CHIPS; i++) { + if (macio_chips[i].of_node) + pmf_do_functions(macio_chips[i].of_node, NULL, 0, + PMF_FLAGS_ON_SLEEP, NULL); + } + if (uninorth_node) + pmf_do_functions(uninorth_node, NULL, 0, + PMF_FLAGS_ON_SLEEP, NULL); + if (unin_hwclock) + pmf_do_functions(unin_hwclock, NULL, 0, + PMF_FLAGS_ON_SLEEP, NULL); +} + +void pmac_pfunc_base_resume(void) +{ + int i; + + if (unin_hwclock) + pmf_do_functions(unin_hwclock, NULL, 0, + PMF_FLAGS_ON_WAKE, NULL); + if (uninorth_node) + pmf_do_functions(uninorth_node, NULL, 0, + PMF_FLAGS_ON_WAKE, NULL); + for (i = 0 ; i < MAX_MACIO_CHIPS; i++) { + if (macio_chips[i].of_node) + pmf_do_functions(macio_chips[i].of_node, NULL, 0, + PMF_FLAGS_ON_WAKE, NULL); + } +} + +#endif /* CONFIG_PM */ Index: linux-work/arch/powerpc/platforms/powermac/pfunc_core.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-work/arch/powerpc/platforms/powermac/pfunc_core.c 2006-01-05 14:56:32.000000000 +1100 @@ -0,0 +1,989 @@ +/* + * + * FIXME: Properly make this race free with refcounting etc... + * + * FIXME: LOCKING !!! + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Debug */ +#define LOG_PARSE(fmt...) +#define LOG_ERROR(fmt...) printk(fmt) +#define LOG_BLOB(t,b,c) +#define DBG(fmt...) printk(fmt) + +/* Command numbers */ +#define PMF_CMD_LIST 0 +#define PMF_CMD_WRITE_GPIO 1 +#define PMF_CMD_READ_GPIO 2 +#define PMF_CMD_WRITE_REG32 3 +#define PMF_CMD_READ_REG32 4 +#define PMF_CMD_WRITE_REG16 5 +#define PMF_CMD_READ_REG16 6 +#define PMF_CMD_WRITE_REG8 7 +#define PMF_CMD_READ_REG8 8 +#define PMF_CMD_DELAY 9 +#define PMF_CMD_WAIT_REG32 10 +#define PMF_CMD_WAIT_REG16 11 +#define PMF_CMD_WAIT_REG8 12 +#define PMF_CMD_READ_I2C 13 +#define PMF_CMD_WRITE_I2C 14 +#define PMF_CMD_RMW_I2C 15 +#define PMF_CMD_GEN_I2C 16 +#define PMF_CMD_SHIFT_BYTES_RIGHT 17 +#define PMF_CMD_SHIFT_BYTES_LEFT 18 +#define PMF_CMD_READ_CFG 19 +#define PMF_CMD_WRITE_CFG 20 +#define PMF_CMD_RMW_CFG 21 +#define PMF_CMD_READ_I2C_SUBADDR 22 +#define PMF_CMD_WRITE_I2C_SUBADDR 23 +#define PMF_CMD_SET_I2C_MODE 24 +#define PMF_CMD_RMW_I2C_SUBADDR 25 +#define PMF_CMD_READ_REG32_MASK_SHR_XOR 26 +#define PMF_CMD_READ_REG16_MASK_SHR_XOR 27 +#define PMF_CMD_READ_REG8_MASK_SHR_XOR 28 +#define PMF_CMD_WRITE_REG32_SHL_MASK 29 +#define PMF_CMD_WRITE_REG16_SHL_MASK 30 +#define PMF_CMD_WRITE_REG8_SHL_MASK 31 +#define PMF_CMD_MASK_AND_COMPARE 32 +#define PMF_CMD_COUNT 33 + +/* This structure holds the state of the parser while walking through + * a function definition + */ +struct pmf_cmd { + const void *cmdptr; + const void *cmdend; + struct pmf_function *func; + void *instdata; + struct pmf_args *args; + int error; +}; + +#if 0 +/* Debug output */ +static void print_blob(const char *title, const void *blob, int bytes) +{ + printk("%s", title); + while(bytes--) { + printk("%02x ", *((u8 *)blob)); + blob += 1; + } + printk("\n"); +} +#endif + +/* + * Parser helpers + */ + +static u32 pmf_next32(struct pmf_cmd *cmd) +{ + u32 value; + if ((cmd->cmdend - cmd->cmdptr) < 4) { + cmd->error = 1; + return 0; + } + value = *((u32 *)cmd->cmdptr); + cmd->cmdptr += 4; + return value; +} + +static const void* pmf_next_blob(struct pmf_cmd *cmd, int count) +{ + const void *value; + if ((cmd->cmdend - cmd->cmdptr) < count) { + cmd->error = 1; + return NULL; + } + value = cmd->cmdptr; + cmd->cmdptr += count; + return value; +} + +/* + * Individual command parsers + */ + +#define PMF_PARSE_CALL(name, cmd, handlers, p...) \ + do { \ + if (cmd->error) \ + return -ENXIO; \ + if (handlers == NULL) \ + return 0; \ + if (handlers->name) \ + return handlers->name(cmd->func, cmd->instdata, \ + cmd->args, p); \ + return -1; \ + } while(0) \ + + +static int pmf_parser_write_gpio(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 value = (u8)pmf_next32(cmd); + u8 mask = (u8)pmf_next32(cmd); + + LOG_PARSE("pmf: write_gpio(value: %02x, mask: %02x)\n", value, mask); + + PMF_PARSE_CALL(write_gpio, cmd, h, value, mask); +} + +static int pmf_parser_read_gpio(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 mask = (u8)pmf_next32(cmd); + int rshift = (int)pmf_next32(cmd); + u8 xor = (u8)pmf_next32(cmd); + + LOG_PARSE("pmf: read_gpio(mask: %02x, rshift: %d, xor: %02x)\n", + mask, rshift, xor); + + PMF_PARSE_CALL(read_gpio, cmd, h, mask, rshift, xor); +} + +static int pmf_parser_write_reg32(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 value = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg32(offset: %08x, value: %08x, mask: %08x)\n", + offset, value, mask); + + PMF_PARSE_CALL(write_reg32, cmd, h, offset, value, mask); +} + +static int pmf_parser_read_reg32(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg32(offset: %08x)\n", offset); + + PMF_PARSE_CALL(read_reg32, cmd, h, offset); +} + + +static int pmf_parser_write_reg16(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u16 value = (u16)pmf_next32(cmd); + u16 mask = (u16)pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg16(offset: %08x, value: %04x, mask: %04x)\n", + offset, value, mask); + + PMF_PARSE_CALL(write_reg16, cmd, h, offset, value, mask); +} + +static int pmf_parser_read_reg16(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg16(offset: %08x)\n", offset); + + PMF_PARSE_CALL(read_reg16, cmd, h, offset); +} + + +static int pmf_parser_write_reg8(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u8 value = (u16)pmf_next32(cmd); + u8 mask = (u16)pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg8(offset: %08x, value: %02x, mask: %02x)\n", + offset, value, mask); + + PMF_PARSE_CALL(write_reg8, cmd, h, offset, value, mask); +} + +static int pmf_parser_read_reg8(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg8(offset: %08x)\n", offset); + + PMF_PARSE_CALL(read_reg8, cmd, h, offset); +} + +static int pmf_parser_delay(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 duration = pmf_next32(cmd); + + LOG_PARSE("pmf: delay(duration: %d us)\n", duration); + + PMF_PARSE_CALL(delay, cmd, h, duration); +} + +static int pmf_parser_wait_reg32(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 value = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: wait_reg32(offset: %08x, comp_value: %08x,mask: %08x)\n", + offset, value, mask); + + PMF_PARSE_CALL(wait_reg32, cmd, h, offset, value, mask); +} + +static int pmf_parser_wait_reg16(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u16 value = (u16)pmf_next32(cmd); + u16 mask = (u16)pmf_next32(cmd); + + LOG_PARSE("pmf: wait_reg16(offset: %08x, comp_value: %04x,mask: %04x)\n", + offset, value, mask); + + PMF_PARSE_CALL(wait_reg16, cmd, h, offset, value, mask); +} + +static int pmf_parser_wait_reg8(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u8 value = (u8)pmf_next32(cmd); + u8 mask = (u8)pmf_next32(cmd); + + LOG_PARSE("pmf: wait_reg8(offset: %08x, comp_value: %02x,mask: %02x)\n", + offset, value, mask); + + PMF_PARSE_CALL(wait_reg8, cmd, h, offset, value, mask); +} + +static int pmf_parser_read_i2c(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 bytes = pmf_next32(cmd); + + LOG_PARSE("pmf: read_i2c(bytes: %ud)\n", bytes); + + PMF_PARSE_CALL(read_i2c, cmd, h, bytes); +} + +static int pmf_parser_write_i2c(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 bytes = pmf_next32(cmd); + const void *blob = pmf_next_blob(cmd, bytes); + + LOG_PARSE("pmf: write_i2c(bytes: %ud) ...\n", bytes); + LOG_BLOB("pmf: data: \n", blob, bytes); + + PMF_PARSE_CALL(write_i2c, cmd, h, bytes, blob); +} + + +static int pmf_parser_rmw_i2c(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 maskbytes = pmf_next32(cmd); + u32 valuesbytes = pmf_next32(cmd); + u32 totalbytes = pmf_next32(cmd); + const void *maskblob = pmf_next_blob(cmd, maskbytes); + const void *valuesblob = pmf_next_blob(cmd, valuesbytes); + + LOG_PARSE("pmf: rmw_i2c(maskbytes: %ud, valuebytes: %ud, " + "totalbytes: %d) ...\n", + maskbytes, valuesbytes, totalbytes); + LOG_BLOB("pmf: mask data: \n", maskblob, maskbytes); + LOG_BLOB("pmf: values data: \n", valuesblob, valuesbytes); + + PMF_PARSE_CALL(rmw_i2c, cmd, h, maskbytes, valuesbytes, totalbytes, + maskblob, valuesblob); +} + +static int pmf_parser_read_cfg(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 bytes = pmf_next32(cmd); + + LOG_PARSE("pmf: read_cfg(offset: %x, bytes: %ud)\n", offset, bytes); + + PMF_PARSE_CALL(read_cfg, cmd, h, offset, bytes); +} + + +static int pmf_parser_write_cfg(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 bytes = pmf_next32(cmd); + const void *blob = pmf_next_blob(cmd, bytes); + + LOG_PARSE("pmf: write_cfg(offset: %x, bytes: %ud)\n", offset, bytes); + LOG_BLOB("pmf: data: \n", blob, bytes); + + PMF_PARSE_CALL(write_cfg, cmd, h, offset, bytes, blob); +} + +static int pmf_parser_rmw_cfg(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 maskbytes = pmf_next32(cmd); + u32 valuesbytes = pmf_next32(cmd); + u32 totalbytes = pmf_next32(cmd); + const void *maskblob = pmf_next_blob(cmd, maskbytes); + const void *valuesblob = pmf_next_blob(cmd, valuesbytes); + + LOG_PARSE("pmf: rmw_cfg(maskbytes: %ud, valuebytes: %ud," + " totalbytes: %d) ...\n", + maskbytes, valuesbytes, totalbytes); + LOG_BLOB("pmf: mask data: \n", maskblob, maskbytes); + LOG_BLOB("pmf: values data: \n", valuesblob, valuesbytes); + + PMF_PARSE_CALL(rmw_cfg, cmd, h, offset, maskbytes, valuesbytes, + totalbytes, maskblob, valuesblob); +} + + +static int pmf_parser_read_i2c_sub(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 subaddr = (u8)pmf_next32(cmd); + u32 bytes = pmf_next32(cmd); + + LOG_PARSE("pmf: read_i2c_sub(subaddr: %x, bytes: %ud)\n", + subaddr, bytes); + + PMF_PARSE_CALL(read_i2c_sub, cmd, h, subaddr, bytes); +} + +static int pmf_parser_write_i2c_sub(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 subaddr = (u8)pmf_next32(cmd); + u32 bytes = pmf_next32(cmd); + const void *blob = pmf_next_blob(cmd, bytes); + + LOG_PARSE("pmf: write_i2c_sub(subaddr: %x, bytes: %ud) ...\n", + subaddr, bytes); + LOG_BLOB("pmf: data: \n", blob, bytes); + + PMF_PARSE_CALL(write_i2c_sub, cmd, h, subaddr, bytes, blob); +} + +static int pmf_parser_set_i2c_mode(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 mode = pmf_next32(cmd); + + LOG_PARSE("pmf: set_i2c_mode(mode: %d)\n", mode); + + PMF_PARSE_CALL(set_i2c_mode, cmd, h, mode); +} + + +static int pmf_parser_rmw_i2c_sub(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 subaddr = (u8)pmf_next32(cmd); + u32 maskbytes = pmf_next32(cmd); + u32 valuesbytes = pmf_next32(cmd); + u32 totalbytes = pmf_next32(cmd); + const void *maskblob = pmf_next_blob(cmd, maskbytes); + const void *valuesblob = pmf_next_blob(cmd, valuesbytes); + + LOG_PARSE("pmf: rmw_i2c_sub(subaddr: %x, maskbytes: %ud, valuebytes: %ud" + ", totalbytes: %d) ...\n", + subaddr, maskbytes, valuesbytes, totalbytes); + LOG_BLOB("pmf: mask data: \n", maskblob, maskbytes); + LOG_BLOB("pmf: values data: \n", valuesblob, valuesbytes); + + PMF_PARSE_CALL(rmw_i2c_sub, cmd, h, subaddr, maskbytes, valuesbytes, + totalbytes, maskblob, valuesblob); +} + +static int pmf_parser_read_reg32_msrx(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 xor = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg32_msrx(offset: %x, mask: %x, shift: %x," + " xor: %x\n", offset, mask, shift, xor); + + PMF_PARSE_CALL(read_reg32_msrx, cmd, h, offset, mask, shift, xor); +} + +static int pmf_parser_read_reg16_msrx(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 xor = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg16_msrx(offset: %x, mask: %x, shift: %x," + " xor: %x\n", offset, mask, shift, xor); + + PMF_PARSE_CALL(read_reg16_msrx, cmd, h, offset, mask, shift, xor); +} +static int pmf_parser_read_reg8_msrx(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 xor = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg8_msrx(offset: %x, mask: %x, shift: %x," + " xor: %x\n", offset, mask, shift, xor); + + PMF_PARSE_CALL(read_reg8_msrx, cmd, h, offset, mask, shift, xor); +} + +static int pmf_parser_write_reg32_slm(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg32_slm(offset: %x, shift: %x, mask: %x\n", + offset, shift, mask); + + PMF_PARSE_CALL(write_reg32_slm, cmd, h, offset, shift, mask); +} + +static int pmf_parser_write_reg16_slm(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg16_slm(offset: %x, shift: %x, mask: %x\n", + offset, shift, mask); + + PMF_PARSE_CALL(write_reg16_slm, cmd, h, offset, shift, mask); +} + +static int pmf_parser_write_reg8_slm(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg8_slm(offset: %x, shift: %x, mask: %x\n", + offset, shift, mask); + + PMF_PARSE_CALL(write_reg8_slm, cmd, h, offset, shift, mask); +} + +static int pmf_parser_mask_and_compare(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 bytes = pmf_next32(cmd); + const void *maskblob = pmf_next_blob(cmd, bytes); + const void *valuesblob = pmf_next_blob(cmd, bytes); + + LOG_PARSE("pmf: mask_and_compare(length: %ud ...\n", bytes); + LOG_BLOB("pmf: mask data: \n", maskblob, bytes); + LOG_BLOB("pmf: values data: \n", valuesblob, bytes); + + PMF_PARSE_CALL(mask_and_compare, cmd, h, + bytes, maskblob, valuesblob); +} + + +typedef int (*pmf_cmd_parser_t)(struct pmf_cmd *cmd, struct pmf_handlers *h); + +static pmf_cmd_parser_t pmf_parsers[PMF_CMD_COUNT] = +{ + NULL, + pmf_parser_write_gpio, + pmf_parser_read_gpio, + pmf_parser_write_reg32, + pmf_parser_read_reg32, + pmf_parser_write_reg16, + pmf_parser_read_reg16, + pmf_parser_write_reg8, + pmf_parser_read_reg8, + pmf_parser_delay, + pmf_parser_wait_reg32, + pmf_parser_wait_reg16, + pmf_parser_wait_reg8, + pmf_parser_read_i2c, + pmf_parser_write_i2c, + pmf_parser_rmw_i2c, + NULL, /* Bogus command */ + NULL, /* Shift bytes right: NYI */ + NULL, /* Shift bytes left: NYI */ + pmf_parser_read_cfg, + pmf_parser_write_cfg, + pmf_parser_rmw_cfg, + pmf_parser_read_i2c_sub, + pmf_parser_write_i2c_sub, + pmf_parser_set_i2c_mode, + pmf_parser_rmw_i2c_sub, + pmf_parser_read_reg32_msrx, + pmf_parser_read_reg16_msrx, + pmf_parser_read_reg8_msrx, + pmf_parser_write_reg32_slm, + pmf_parser_write_reg16_slm, + pmf_parser_write_reg8_slm, + pmf_parser_mask_and_compare, +}; + +struct pmf_device { + struct list_head link; + struct device_node *node; + struct pmf_handlers *handlers; + struct list_head functions; + struct kref ref; +}; + +static LIST_HEAD(pmf_devices); +static spinlock_t pmf_lock = SPIN_LOCK_UNLOCKED; + +static void pmf_release_device(struct kref *kref) +{ + struct pmf_device *dev = container_of(kref, struct pmf_device, ref); + kfree(dev); +} + +static inline void pmf_put_device(struct pmf_device *dev) +{ + kref_put(&dev->ref, pmf_release_device); +} + +static inline struct pmf_device *pmf_get_device(struct pmf_device *dev) +{ + kref_get(&dev->ref); + return dev; +} + +static inline struct pmf_device *pmf_find_device(struct device_node *np) +{ + struct pmf_device *dev; + + list_for_each_entry(dev, &pmf_devices, link) { + if (dev->node == np) + return pmf_get_device(dev); + } + return NULL; +} + +static int pmf_parse_one(struct pmf_function *func, + struct pmf_handlers *handlers, + void *instdata, struct pmf_args *args) +{ + struct pmf_cmd cmd; + u32 ccode; + int count, rc; + + cmd.cmdptr = func->data; + cmd.cmdend = func->data + func->length; + cmd.func = func; + cmd.instdata = instdata; + cmd.args = args; + cmd.error = 0; + + LOG_PARSE("pmf: func %s, %d bytes, %s...\n", + func->name, func->length, + handlers ? "executing" : "parsing"); + + /* One subcommand to parse for now */ + count = 1; + + while(count-- && cmd.cmdptr < cmd.cmdend) { + /* Get opcode */ + ccode = pmf_next32(&cmd); + /* Check if we are hitting a command list, fetch new count */ + if (ccode == 0) { + count = pmf_next32(&cmd) - 1; + ccode = pmf_next32(&cmd); + } + if (cmd.error) { + LOG_ERROR("pmf: parse error, not enough data\n"); + return -ENXIO; + } + if (ccode >= PMF_CMD_COUNT) { + LOG_ERROR("pmf: command code %d unknown !\n", ccode); + return -ENXIO; + } + if (pmf_parsers[ccode] == NULL) { + LOG_ERROR("pmf: no parser for command %d !\n", ccode); + return -ENXIO; + } + rc = pmf_parsers[ccode](&cmd, handlers); + if (rc != 0) { + LOG_ERROR("pmf: parser for command %d returned" + " error %d\n", ccode, rc); + return rc; + } + } + + /* We are doing an initial parse pass, we need to adjust the size */ + if (handlers == NULL) + func->length = cmd.cmdptr - func->data; + + return 0; +} + +static int pmf_add_function_prop(struct pmf_device *dev, void *driverdata, + const char *name, u32 *data, + unsigned int length) +{ + int count = 0; + struct pmf_function *func = NULL; + + DBG("pmf: Adding functions for platform-do-%s\n", name); + + while (length >= 12) { + /* Allocate a structure */ + func = kzalloc(sizeof(struct pmf_function), GFP_KERNEL); + if (func == NULL) + goto bail; + kref_init(&func->ref); + INIT_LIST_HEAD(&func->irq_clients); + func->node = dev->node; + func->driver_data = driverdata; + func->name = name; + func->phandle = data[0]; + func->flags = data[1]; + data += 2; + length -= 8; + func->data = data; + func->length = length; + func->dev = dev; + DBG("pmf: idx %d: flags=%08x, phandle=%08x " + " %d bytes remaining, parsing...\n", + count+1, func->flags, func->phandle, length); + if (pmf_parse_one(func, NULL, NULL, NULL)) { + kfree(func); + goto bail; + } + length -= func->length; + data = (u32 *)(((u8 *)data) + func->length); + list_add(&func->link, &dev->functions); + pmf_get_device(dev); + count++; + } + bail: + DBG("pmf: Added %d functions\n", count); + + return count; +} + +static int pmf_add_functions(struct pmf_device *dev, void *driverdata) +{ + struct property *pp; +#define PP_PREFIX "platform-do-" + const int plen = strlen(PP_PREFIX); + int count = 0; + + for (pp = dev->node->properties; pp != 0; pp = pp->next) { + char *name; + if (strncmp(pp->name, PP_PREFIX, plen) != 0) + continue; + name = pp->name + plen; + if (strlen(name) && pp->length >= 12) + count += pmf_add_function_prop(dev, driverdata, name, + (u32 *)pp->value, + pp->length); + } + return count; +} + + +int pmf_register_driver(struct device_node *np, + struct pmf_handlers *handlers, + void *driverdata) +{ + struct pmf_device *dev; + unsigned long flags; + int rc = 0; + + if (handlers == NULL) + return -EINVAL; + + DBG("pmf: registering driver for node %s\n", np->full_name); + + spin_lock_irqsave(&pmf_lock, flags); + dev = pmf_find_device(np); + spin_unlock_irqrestore(&pmf_lock, flags); + if (dev != NULL) { + DBG("pmf: already there !\n"); + pmf_put_device(dev); + return -EBUSY; + } + + dev = kzalloc(sizeof(struct pmf_device), GFP_KERNEL); + if (dev == NULL) { + DBG("pmf: no memory !\n"); + return -ENOMEM; + } + kref_init(&dev->ref); + dev->node = of_node_get(np); + dev->handlers = handlers; + INIT_LIST_HEAD(&dev->functions); + + rc = pmf_add_functions(dev, driverdata); + if (rc == 0) { + DBG("pmf: no functions, disposing.. \n"); + of_node_put(np); + kfree(dev); + return -ENODEV; + } + + spin_lock_irqsave(&pmf_lock, flags); + list_add(&dev->link, &pmf_devices); + spin_unlock_irqrestore(&pmf_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(pmf_register_driver); + +struct pmf_function *pmf_get_function(struct pmf_function *func) +{ + if (!try_module_get(func->dev->handlers->owner)) + return NULL; + kref_get(&func->ref); + return func; +} +EXPORT_SYMBOL_GPL(pmf_get_function); + +static void pmf_release_function(struct kref *kref) +{ + struct pmf_function *func = + container_of(kref, struct pmf_function, ref); + pmf_put_device(func->dev); + kfree(func); +} + +static inline void __pmf_put_function(struct pmf_function *func) +{ + kref_put(&func->ref, pmf_release_function); +} + +void pmf_put_function(struct pmf_function *func) +{ + if (func == NULL) + return; + module_put(func->dev->handlers->owner); + __pmf_put_function(func); +} +EXPORT_SYMBOL_GPL(pmf_put_function); + +void pmf_unregister_driver(struct device_node *np) +{ + struct pmf_device *dev; + unsigned long flags; + + DBG("pmf: unregistering driver for node %s\n", np->full_name); + + spin_lock_irqsave(&pmf_lock, flags); + dev = pmf_find_device(np); + if (dev == NULL) { + DBG("pmf: not such driver !\n"); + spin_unlock_irqrestore(&pmf_lock, flags); + return; + } + list_del(&dev->link); + + while(!list_empty(&dev->functions)) { + struct pmf_function *func = + list_entry(dev->functions.next, typeof(*func), link); + list_del(&func->link); + __pmf_put_function(func); + } + + pmf_put_device(dev); + spin_unlock_irqrestore(&pmf_lock, flags); +} +EXPORT_SYMBOL_GPL(pmf_unregister_driver); + +struct pmf_function *__pmf_find_function(struct device_node *target, + const char *name, u32 flags) +{ + struct device_node *actor = of_node_get(target); + struct pmf_device *dev; + struct pmf_function *func, *result = NULL; + char fname[64]; + u32 *prop, ph; + + /* + * Look for a "platform-*" function reference. If we can't find + * one, then we fallback to a direct call attempt + */ + snprintf(fname, 63, "platform-%s", name); + prop = (u32 *)get_property(target, fname, NULL); + if (prop == NULL) + goto find_it; + ph = *prop; + if (ph == 0) + goto find_it; + + /* + * Ok, now try to find the actor. If we can't find it, we fail, + * there is no point in falling back there + */ + of_node_put(actor); + actor = of_find_node_by_phandle(ph); + if (actor == NULL) + return NULL; + find_it: + dev = pmf_find_device(actor); + if (dev == NULL) + return NULL; + + list_for_each_entry(func, &dev->functions, link) { + if (name && strcmp(name, func->name)) + continue; + if (func->phandle && target->node != func->phandle) + continue; + if ((func->flags & flags) == 0) + continue; + result = func; + break; + } + of_node_put(actor); + pmf_put_device(dev); + return result; +} + + +int pmf_register_irq_client(struct device_node *target, + const char *name, + struct pmf_irq_client *client) +{ + struct pmf_function *func; + unsigned long flags; + + spin_lock_irqsave(&pmf_lock, flags); + func = __pmf_find_function(target, name, PMF_FLAGS_INT_GEN); + if (func == NULL) { + spin_unlock_irqrestore(&pmf_lock, flags); + return -ENODEV; + } + list_add(&client->link, &func->irq_clients); + spin_unlock_irqrestore(&pmf_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(pmf_register_irq_client); + +void pmf_unregister_irq_client(struct device_node *np, + const char *name, + struct pmf_irq_client *client) +{ + unsigned long flags; + + spin_lock_irqsave(&pmf_lock, flags); + list_del(&client->link); + spin_unlock_irqrestore(&pmf_lock, flags); +} +EXPORT_SYMBOL_GPL(pmf_unregister_irq_client); + + +void pmf_do_irq(struct pmf_function *func) +{ + unsigned long flags; + struct pmf_irq_client *client; + + /* For now, using a spinlock over the whole function. Can be made + * to drop the lock using 2 lists if necessary + */ + spin_lock_irqsave(&pmf_lock, flags); + list_for_each_entry(client, &func->irq_clients, link) { + if (!try_module_get(client->owner)) + continue; + client->handler(client->data); + module_put(client->owner); + } + spin_unlock_irqrestore(&pmf_lock, flags); +} +EXPORT_SYMBOL_GPL(pmf_do_irq); + + +int pmf_call_one(struct pmf_function *func, struct pmf_args *args) +{ + struct pmf_device *dev = func->dev; + void *instdata = NULL; + int rc = 0; + + DBG(" ** pmf_call_one(%s/%s) **\n", dev->node->full_name, func->name); + + if (dev->handlers->begin) + instdata = dev->handlers->begin(func, args); + rc = pmf_parse_one(func, dev->handlers, instdata, args); + if (dev->handlers->end) + dev->handlers->end(func, instdata); + + return rc; +} +EXPORT_SYMBOL_GPL(pmf_call_one); + +int pmf_do_functions(struct device_node *np, const char *name, + u32 phandle, u32 fflags, struct pmf_args *args) +{ + struct pmf_device *dev; + struct pmf_function *func, *tmp; + unsigned long flags; + int rc = -ENODEV; + + spin_lock_irqsave(&pmf_lock, flags); + + dev = pmf_find_device(np); + if (dev == NULL) { + spin_unlock_irqrestore(&pmf_lock, flags); + return -ENODEV; + } + list_for_each_entry_safe(func, tmp, &dev->functions, link) { + if (name && strcmp(name, func->name)) + continue; + if (phandle && func->phandle && phandle != func->phandle) + continue; + if ((func->flags & fflags) == 0) + continue; + if (pmf_get_function(func) == NULL) + continue; + spin_unlock_irqrestore(&pmf_lock, flags); + rc = pmf_call_one(func, args); + pmf_put_function(func); + spin_lock_irqsave(&pmf_lock, flags); + } + pmf_put_device(dev); + spin_unlock_irqrestore(&pmf_lock, flags); + + return rc; +} +EXPORT_SYMBOL_GPL(pmf_do_functions); + + +struct pmf_function *pmf_find_function(struct device_node *target, + const char *name) +{ + struct pmf_function *func; + unsigned long flags; + + spin_lock_irqsave(&pmf_lock, flags); + func = __pmf_find_function(target, name, PMF_FLAGS_ON_DEMAND); + if (func) + func = pmf_get_function(func); + spin_unlock_irqrestore(&pmf_lock, flags); + return func; +} +EXPORT_SYMBOL_GPL(pmf_find_function); + +int pmf_call_function(struct device_node *target, const char *name, + struct pmf_args *args) +{ + struct pmf_function *func = pmf_find_function(target, name); + int rc; + + if (func == NULL) + return -ENODEV; + + rc = pmf_call_one(func, args); + pmf_put_function(func); + return rc; +} +EXPORT_SYMBOL_GPL(pmf_call_function); + Index: linux-work/include/asm-powerpc/pmac_feature.h =================================================================== --- linux-work.orig/include/asm-powerpc/pmac_feature.h 2006-01-04 11:55:10.000000000 +1100 +++ linux-work/include/asm-powerpc/pmac_feature.h 2006-01-04 16:34:13.000000000 +1100 @@ -318,10 +318,6 @@ extern void pmac_suspend_agp_for_card(struct pci_dev *dev); extern void pmac_resume_agp_for_card(struct pci_dev *dev); -/* Used by the via-pmu driver for suspend/resume - */ -extern void pmac_tweak_clock_spreading(int enable); - /* * The part below is for use by macio_asic.c only, do not rely * on the data structures or constants below in a normal driver @@ -378,5 +374,24 @@ #define MACIO_IN8(r) (in_8(MACIO_FCR8(macio,r))) #define MACIO_OUT8(r,v) (out_8(MACIO_FCR8(macio,r), (v))) +/* + * Those are exported by pmac feature for internal use by arch code + * only like the platform function callbacks, do not use directly in drivers + */ +extern spinlock_t feature_lock; +extern struct device_node *uninorth_node; +extern u32 __iomem *uninorth_base; + +/* + * Uninorth reg. access. Note that Uni-N regs are big endian + */ + +#define UN_REG(r) (uninorth_base + ((r) >> 2)) +#define UN_IN(r) (in_be32(UN_REG(r))) +#define UN_OUT(r,v) (out_be32(UN_REG(r), (v))) +#define UN_BIS(r,v) (UN_OUT((r), UN_IN(r) | (v))) +#define UN_BIC(r,v) (UN_OUT((r), UN_IN(r) & ~(v))) + + #endif /* __PPC_ASM_PMAC_FEATURE_H */ #endif /* __KERNEL__ */ Index: linux-work/arch/powerpc/platforms/powermac/smp.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/smp.c 2006-01-04 11:55:10.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/smp.c 2006-01-05 11:54:05.000000000 +1100 @@ -52,8 +52,9 @@ #include #include #include +#include -#undef DEBUG +#define DEBUG #ifdef DEBUG #define DBG(fmt...) udbg_printf(fmt) @@ -62,6 +63,7 @@ #endif extern void __secondary_start_pmac_0(void); +extern int pmac_pfunc_base_install(void); #ifdef CONFIG_PPC32 @@ -482,7 +484,7 @@ /* * G5s enable/disable the timebase via an i2c-connected clock chip. */ -static struct device_node *pmac_tb_clock_chip_host; +static struct pmac_i2c_bus *pmac_tb_clock_chip_host; static u8 pmac_tb_pulsar_addr; static void smp_core99_cypress_tb_freeze(int freeze) @@ -493,20 +495,20 @@ /* Strangely, the device-tree says address is 0xd2, but darwin * accesses 0xd0 ... */ - pmac_low_i2c_setmode(pmac_tb_clock_chip_host, - pmac_low_i2c_mode_combined); - rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host, - 0xd0 | pmac_low_i2c_read, - 0x81, &data, 1); + pmac_i2c_setmode(pmac_tb_clock_chip_host, + pmac_i2c_mode_combined); + rc = pmac_i2c_xfer(pmac_tb_clock_chip_host, + 0xd0 | pmac_i2c_read, + 1, 0x81, &data, 1); if (rc != 0) goto bail; data = (data & 0xf3) | (freeze ? 0x00 : 0x0c); - pmac_low_i2c_setmode(pmac_tb_clock_chip_host, pmac_low_i2c_mode_stdsub); - rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host, - 0xd0 | pmac_low_i2c_write, - 0x81, &data, 1); + pmac_i2c_setmode(pmac_tb_clock_chip_host, pmac_i2c_mode_stdsub); + rc = pmac_i2c_xfer(pmac_tb_clock_chip_host, + 0xd0 | pmac_i2c_write, + 1, 0x81, &data, 1); bail: if (rc != 0) { @@ -522,20 +524,20 @@ u8 data; int rc; - pmac_low_i2c_setmode(pmac_tb_clock_chip_host, - pmac_low_i2c_mode_combined); - rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host, - pmac_tb_pulsar_addr | pmac_low_i2c_read, - 0x2e, &data, 1); + pmac_i2c_setmode(pmac_tb_clock_chip_host, + pmac_i2c_mode_combined); + rc = pmac_i2c_xfer(pmac_tb_clock_chip_host, + pmac_tb_pulsar_addr | pmac_i2c_read, + 1, 0x2e, &data, 1); if (rc != 0) goto bail; data = (data & 0x88) | (freeze ? 0x11 : 0x22); - pmac_low_i2c_setmode(pmac_tb_clock_chip_host, pmac_low_i2c_mode_stdsub); - rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host, - pmac_tb_pulsar_addr | pmac_low_i2c_write, - 0x2e, &data, 1); + pmac_i2c_setmode(pmac_tb_clock_chip_host, pmac_i2c_mode_stdsub); + rc = pmac_i2c_xfer(pmac_tb_clock_chip_host, + pmac_tb_pulsar_addr | pmac_i2c_write, + 1, 0x2e, &data, 1); bail: if (rc != 0) { printk(KERN_ERR "Pulsar Timebase %s rc: %d\n", @@ -560,13 +562,15 @@ if (!ok) continue; + pmac_tb_clock_chip_host = pmac_i2c_find_bus(cc); + if (pmac_tb_clock_chip_host == NULL) + continue; reg = (u32 *)get_property(cc, "reg", NULL); if (reg == NULL) continue; - switch (*reg) { case 0xd2: - if (device_is_compatible(cc, "pulsar-legacy-slewing")) { + if (device_is_compatible(cc,"pulsar-legacy-slewing")) { pmac_tb_freeze = smp_core99_pulsar_tb_freeze; pmac_tb_pulsar_addr = 0xd2; name = "Pulsar"; @@ -585,37 +589,44 @@ break; } if (pmac_tb_freeze != NULL) { - struct device_node *p = of_get_parent(cc); - of_node_put(cc); - while(p && strcmp(p->type, "i2c")) { - cc = of_get_parent(p); - of_node_put(p); - p = cc; - } - if (p == NULL) - goto no_i2c_sync; /* Open i2c bus for synchronous access */ - if (pmac_low_i2c_open(p, 0)) { - printk(KERN_ERR "Failed top open i2c bus %s for clock" - " sync, fallback to software sync !\n", - p->full_name); - of_node_put(p); + if (pmac_i2c_open(pmac_tb_clock_chip_host, 1)) { + printk(KERN_ERR "Failed top open i2c bus for clock" + " sync, fallback to software sync !\n"); goto no_i2c_sync; } - pmac_tb_clock_chip_host = p; printk(KERN_INFO "Processor timebase sync using %s i2c clock\n", name); return; } no_i2c_sync: pmac_tb_freeze = NULL; + pmac_tb_clock_chip_host = NULL; } -#endif /* CONFIG_PPC64 */ /* - * SMP G4 and newer G5 use a GPIO to enable/disable the timebase. + * Newer G5s uses a platform function + */ + +static void smp_core99_pfunc_tb_freeze(int freeze) +{ + struct device_node *cpus; + struct pmf_args args; + + cpus = of_find_node_by_path("/cpus"); + BUG_ON(cpus == NULL); + args.count = 1; + args.u[0].v = !freeze; + pmf_call_function(cpus, "cpu-timebase", &args); + of_node_put(cpus); +} + +#else /* CONFIG_PPC64 */ + +/* + * SMP G4 use a GPIO to enable/disable the timebase. */ static unsigned int core99_tb_gpio; /* Timebase freeze GPIO */ @@ -629,6 +640,9 @@ pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, core99_tb_gpio, 0); } + +#endif /* !CONFIG_PPC64 */ + /* L2 and L3 cache settings to pass from CPU0 to CPU1 on G4 cpus */ volatile static long int core99_l2_cache; volatile static long int core99_l3_cache; @@ -674,19 +688,15 @@ machine_is_compatible("RackMac3,1")) smp_core99_setup_i2c_hwsync(ncpus); - /* GPIO based HW sync on recent G5s */ + /* pfunc based HW sync on recent G5s */ if (pmac_tb_freeze == NULL) { - struct device_node *np = - of_find_node_by_name(NULL, "timebase-enable"); - u32 *reg = (u32 *)get_property(np, "reg", NULL); - - if (np && reg && !strcmp(np->type, "gpio")) { - core99_tb_gpio = *reg; - if (core99_tb_gpio < 0x50) - core99_tb_gpio += 0x50; - pmac_tb_freeze = smp_core99_gpio_tb_freeze; + struct device_node *cpus = + of_find_node_by_path("/cpus"); + if (cpus && + get_property(cpus, "platform-cpu-timebase", NULL)) { + pmac_tb_freeze = smp_core99_pfunc_tb_freeze; printk(KERN_INFO "Processor timebase sync using" - " GPIO 0x%02x\n", core99_tb_gpio); + " platform function\n"); } } @@ -752,8 +762,19 @@ if (ncpus <= 1) return 1; + /* We need to perform some early initialisations before we can start + * setting up SMP as we are running before initcalls + */ + pmac_pfunc_base_install(); + pmac_i2c_init(); + + /* Setup various bits like timebase sync method, ability to nap, ... */ smp_core99_setup(ncpus); + + /* Install IPIs */ mpic_request_ipis(); + + /* Collect l2cr and l3cr values from CPU 0 */ core99_init_caches(0); return ncpus; @@ -817,7 +838,7 @@ /* Close i2c bus if it was used for tb sync */ if (pmac_tb_clock_chip_host) { - pmac_low_i2c_close(pmac_tb_clock_chip_host); + pmac_i2c_close(pmac_tb_clock_chip_host); pmac_tb_clock_chip_host = NULL; } Index: linux-work/arch/powerpc/platforms/powermac/low_i2c.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/low_i2c.c 2005-11-24 17:21:41.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/low_i2c.c 2006-01-05 15:11:37.000000000 +1100 @@ -1,22 +1,34 @@ /* - * arch/ppc/platforms/pmac_low_i2c.c + * arch/powerpc/platforms/powermac/low_i2c.c * - * Copyright (C) 2003 Ben. Herrenschmidt (benh at kernel.crashing.org) + * Copyright (C) 2003-2005 Ben. Herrenschmidt (benh at kernel.crashing.org) * * 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 file contains some low-level i2c access routines that - * need to be used by various bits of the PowerMac platform code - * at times where the real asynchronous & interrupt driven driver - * cannot be used. The API borrows some semantics from the darwin - * driver in order to ease the implementation of the platform - * properties parser + * The linux i2c layer isn't completely suitable for our needs for various + * reasons ranging from too late initialisation to semantics not perfectly + * matching some requirements of the apple platform functions etc... + * + * This file thus provides a simple low level unified i2c interface for + * powermac that covers the various types of i2c busses used in Apple machines. + * For now, keywest, PMU and SMU, though we could add Cuda, or other bit + * banging busses found on older chipstes in earlier machines if we ever need + * one of them. + * + * The drivers in this file are synchronous/blocking. In addition, the + * keywest one is fairly slow due to the use of msleep instead of interrupts + * as the interrupt is currently used by i2c-keywest. In the long run, we + * might want to get rid of those high-level interfaces to linux i2c layer + * either completely (converting all drivers) or replacing them all with a + * single stub driver on top of this one. Once done, the interrupt will be + * available for our use. */ -#undef DEBUG +#define DEBUG +#undef DEBUG_LOW #include #include @@ -25,15 +37,16 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #include -#define MAX_LOW_I2C_HOST 4 - #ifdef DEBUG #define DBG(x...) do {\ printk(KERN_DEBUG "low_i2c:" x); \ @@ -42,49 +55,54 @@ #define DBG(x...) #endif -struct low_i2c_host; - -typedef int (*low_i2c_func_t)(struct low_i2c_host *host, u8 addr, u8 sub, u8 *data, int len); +#ifdef DEBUG_LOW +#define DBG_LOW(x...) do {\ + printk(KERN_DEBUG "low_i2c:" x); \ + } while(0) +#else +#define DBG_LOW(x...) +#endif -struct low_i2c_host +/* + * A bus structure. Each bus in the system has such a structure associated. + */ +struct pmac_i2c_bus { - struct device_node *np; /* OF device node */ - struct semaphore mutex; /* Access mutex for use by i2c-keywest */ - low_i2c_func_t func; /* Access function */ - unsigned int is_open : 1; /* Poor man's access control */ - int mode; /* Current mode */ - int channel; /* Current channel */ - int num_channels; /* Number of channels */ - void __iomem *base; /* For keywest-i2c, base address */ - int bsteps; /* And register stepping */ - int speed; /* And speed */ + struct list_head link; + struct device_node *controller; + struct device_node *busnode; + int type; + int flags; + struct i2c_adapter *adapter; + void *hostdata; + int channel; /* some hosts have multiple */ + int mode; /* current mode */ + struct semaphore sem; + int opened; + int polled; /* open mode */ + + /* ops */ + int (*open)(struct pmac_i2c_bus *bus); + void (*close)(struct pmac_i2c_bus *bus); + int (*xfer)(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len); }; -static struct low_i2c_host low_i2c_hosts[MAX_LOW_I2C_HOST]; - -/* No locking is necessary on allocation, we are running way before - * anything can race with us - */ -static struct low_i2c_host *find_low_i2c_host(struct device_node *np) -{ - int i; - - for (i = 0; i < MAX_LOW_I2C_HOST; i++) - if (low_i2c_hosts[i].np == np) - return &low_i2c_hosts[i]; - return NULL; -} +static LIST_HEAD(pmac_i2c_busses); /* - * - * i2c-keywest implementation (UniNorth, U2, U3, Keylargo's) - * + * Keywest implementation */ -/* - * Keywest i2c definitions borrowed from drivers/i2c/i2c-keywest.h, - * should be moved somewhere in include/asm-ppc/ - */ +struct pmac_i2c_host_kw +{ + struct semaphore mutex; /* Access mutex for use by + * i2c-keywest */ + void __iomem *base; /* register base address */ + int bsteps; /* register stepping */ + int speed; /* speed */ +}; + /* Register indices */ typedef enum { reg_mode = 0, @@ -153,52 +171,56 @@ "state_dead" }; -static inline u8 __kw_read_reg(struct low_i2c_host *host, reg_t reg) +static inline u8 __kw_read_reg(struct pmac_i2c_bus *bus, reg_t reg) { + struct pmac_i2c_host_kw *host = bus->hostdata; return readb(host->base + (((unsigned int)reg) << host->bsteps)); } -static inline void __kw_write_reg(struct low_i2c_host *host, reg_t reg, u8 val) +static inline void __kw_write_reg(struct pmac_i2c_bus *bus, reg_t reg, u8 val) { + struct pmac_i2c_host_kw *host = bus->hostdata; writeb(val, host->base + (((unsigned)reg) << host->bsteps)); - (void)__kw_read_reg(host, reg_subaddr); + (void)__kw_read_reg(bus, reg_subaddr); } -#define kw_write_reg(reg, val) __kw_write_reg(host, reg, val) -#define kw_read_reg(reg) __kw_read_reg(host, reg) - +#define kw_write_reg(reg, val) __kw_write_reg(bus, reg, val) +#define kw_read_reg(reg) __kw_read_reg(bus, reg) -/* Don't schedule, the g5 fan controller is too - * timing sensitive - */ -static u8 kw_wait_interrupt(struct low_i2c_host* host) +static u8 kw_i2c_wait_interrupt(struct pmac_i2c_bus* bus) { int i, j; u8 isr; - for (i = 0; i < 100000; i++) { + for (i = 0; i < 1000; i++) { isr = kw_read_reg(reg_isr) & KW_I2C_IRQ_MASK; if (isr != 0) return isr; /* This code is used with the timebase frozen, we cannot rely - * on udelay ! For now, just use a bogus loop + * on udelay nor schedule when in polled mode ! + * For now, just use a bogus loop.... */ - for (j = 1; j < 10000; j++) - mb(); + if (bus->polled) { + for (j = 1; j < 1000000; j++) + mb(); + } else + msleep(1); } return isr; } -static int kw_handle_interrupt(struct low_i2c_host *host, int state, int rw, int *rc, u8 **data, int *len, u8 isr) +static int kw_i2c_handle_interrupt(struct pmac_i2c_bus *bus, int state, int rw, + int *rc, u8 **data, int *len, u8 isr) { u8 ack; - DBG("kw_handle_interrupt(%s, isr: %x)\n", __kw_state_names[state], isr); + DBG_LOW("kw_handle_interrupt(%s, isr: %x)\n", + __kw_state_names[state], isr); if (isr == 0) { if (state != state_stop) { - DBG("KW: Timeout !\n"); + DBG_LOW("KW: Timeout !\n"); *rc = -EIO; goto stop; } @@ -220,15 +242,16 @@ *rc = -EIO; goto stop; } - if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { + if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { *rc = -ENODEV; - DBG("KW: NAK on address\n"); + DBG_LOW("KW: NAK on address\n"); return state_stop; } else { if (rw) { state = state_read; if (*len > 1) - kw_write_reg(reg_control, KW_I2C_CTL_AAK); + kw_write_reg(reg_control, + KW_I2C_CTL_AAK); } else { state = state_write; kw_write_reg(reg_data, **data); @@ -250,7 +273,7 @@ } else if (state == state_write) { ack = kw_read_reg(reg_status); if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { - DBG("KW: nack on data write\n"); + DBG_LOW("KW: nack on data write\n"); *rc = -EIO; goto stop; } else if (*len) { @@ -291,35 +314,57 @@ return state_stop; } -static int keywest_low_i2c_func(struct low_i2c_host *host, u8 addr, u8 subaddr, u8 *data, int len) +static int kw_i2c_open(struct pmac_i2c_bus *bus) { + struct pmac_i2c_host_kw *host = bus->hostdata; + down(&host->mutex); + return 0; +} + +static void kw_i2c_close(struct pmac_i2c_bus *bus) +{ + struct pmac_i2c_host_kw *host = bus->hostdata; + up(&host->mutex); +} + +static int kw_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len) +{ + struct pmac_i2c_host_kw *host = bus->hostdata; u8 mode_reg = host->speed; int state = state_addr; int rc = 0; /* Setup mode & subaddress if any */ - switch(host->mode) { - case pmac_low_i2c_mode_dumb: - printk(KERN_ERR "low_i2c: Dumb mode not supported !\n"); + switch(bus->mode) { + case pmac_i2c_mode_dumb: return -EINVAL; - case pmac_low_i2c_mode_std: + case pmac_i2c_mode_std: mode_reg |= KW_I2C_MODE_STANDARD; + if (subsize != 0) + return -EINVAL; break; - case pmac_low_i2c_mode_stdsub: + case pmac_i2c_mode_stdsub: mode_reg |= KW_I2C_MODE_STANDARDSUB; + if (subsize != 1) + return -EINVAL; break; - case pmac_low_i2c_mode_combined: + case pmac_i2c_mode_combined: mode_reg |= KW_I2C_MODE_COMBINED; + if (subsize != 1) + return -EINVAL; break; } /* Setup channel & clear pending irqs */ kw_write_reg(reg_isr, kw_read_reg(reg_isr)); - kw_write_reg(reg_mode, mode_reg | (host->channel << 4)); + kw_write_reg(reg_mode, mode_reg | (bus->channel << 4)); kw_write_reg(reg_status, 0); - /* Set up address and r/w bit */ - kw_write_reg(reg_addr, addr); + /* Set up address and r/w bit, strip possible stale bus number from + * address top bits + */ + kw_write_reg(reg_addr, addrdir & 0xff); /* Set up the sub address */ if ((mode_reg & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_STANDARDSUB @@ -330,27 +375,27 @@ kw_write_reg(reg_ier, 0 /*KW_I2C_IRQ_MASK*/); kw_write_reg(reg_control, KW_I2C_CTL_XADDR); - /* State machine, to turn into an interrupt handler */ + /* State machine, to turn into an interrupt handler in the future */ while(state != state_idle) { - u8 isr = kw_wait_interrupt(host); - state = kw_handle_interrupt(host, state, addr & 1, &rc, &data, &len, isr); + u8 isr = kw_i2c_wait_interrupt(bus); + state = kw_i2c_handle_interrupt(bus, state, addrdir & 1, &rc, + &data, &len, isr); } return rc; } -static void keywest_low_i2c_add(struct device_node *np) +static struct pmac_i2c_host_kw *__init kw_i2c_host_init(struct device_node *np) { - struct low_i2c_host *host = find_low_i2c_host(NULL); + struct pmac_i2c_host_kw *host; u32 *psteps, *prate, *addrp, steps; - struct device_node *parent; + host = kzalloc(sizeof(struct pmac_i2c_host_kw), GFP_KERNEL); if (host == NULL) { printk(KERN_ERR "low_i2c: Can't allocate host for %s\n", np->full_name); - return; + return NULL; } - memset(host, 0, sizeof(*host)); /* Apple is kind enough to provide a valid AAPL,address property * on all i2c keywest nodes so far ... we would have to fallback @@ -360,18 +405,14 @@ if (addrp == NULL) { printk(KERN_ERR "low_i2c: Can't find address for %s\n", np->full_name); - return; + kfree(host); + return NULL; } init_MUTEX(&host->mutex); - host->np = of_node_get(np); psteps = (u32 *)get_property(np, "AAPL,address-step", NULL); steps = psteps ? (*psteps) : 0x10; for (host->bsteps = 0; (steps & 0x01) == 0; host->bsteps++) steps >>= 1; - parent = of_get_parent(np); - host->num_channels = 1; - if (parent && parent->name[0] == 'u') - host->num_channels = 2; /* Select interface rate */ host->speed = KW_I2C_MODE_25KHZ; prate = (u32 *)get_property(np, "AAPL,i2c-rate", NULL); @@ -387,15 +428,92 @@ break; } - printk(KERN_INFO "low_i2c: Bus %s found at 0x%08x, %d channels," - " speed = %d KHz\n", - np->full_name, *addrp, host->num_channels, prate ? *prate : 25); - - host->mode = pmac_low_i2c_mode_std; + printk(KERN_INFO "KeyWest i2c @0x%08x %s\n", *addrp, np->full_name); host->base = ioremap((*addrp), 0x1000); - host->func = keywest_low_i2c_func; + + return host; +} + + +static void __init kw_i2c_add(struct pmac_i2c_host_kw *host, + struct device_node *controller, + struct device_node *busnode, + int channel) +{ + struct pmac_i2c_bus *bus; + + bus = kzalloc(sizeof(struct pmac_i2c_bus), GFP_KERNEL); + if (bus == NULL) + return; + + bus->controller = of_node_get(controller); + bus->busnode = of_node_get(busnode); + bus->type = pmac_i2c_bus_keywest; + bus->hostdata = host; + bus->channel = channel; + bus->mode = pmac_i2c_mode_std; + bus->open = kw_i2c_open; + bus->close = kw_i2c_close; + bus->xfer = kw_i2c_xfer; + init_MUTEX(&bus->sem); + if (controller == busnode) + bus->flags = pmac_i2c_multibus; + list_add(&bus->link, &pmac_i2c_busses); + + printk(KERN_INFO " channel %d bus %s\n", channel, + (controller == busnode) ? "" : busnode->full_name); +} + +static void __init kw_i2c_probe(void) +{ + struct device_node *np, *child, *parent; + + /* Probe keywest-i2c busses */ + for (np = NULL; + (np = of_find_compatible_node(np, "i2c","keywest-i2c")) != NULL;){ + struct pmac_i2c_host_kw *host; + int multibus, chans, i; + + /* Found one, init a host structure */ + host = kw_i2c_host_init(np); + if (host == NULL) + continue; + + /* Now check if we have a multibus setup (old style) or if we + * have proper bus nodes. Note that the "new" way (proper bus + * nodes) might cause us to not create some busses that are + * kept hidden in the device-tree. In the future, we might + * want to work around that by creating busses without a node + * but not for now + */ + child = of_get_next_child(np, NULL); + multibus = !child || strcmp(child->name, "i2c-bus"); + of_node_put(child); + + /* For a multibus setup, we get the bus count based on the + * parent type + */ + if (multibus) { + parent = of_get_parent(np); + if (parent == NULL) + continue; + chans = parent->name[0] == 'u' ? 2 : 1; + for (i = 0; i < chans; i++) + kw_i2c_add(host, np, np, i); + } else { + for (child = NULL; + (child = of_get_next_child(np, child)) != NULL;) { + u32 *reg = + (u32 *)get_property(child, "reg", NULL); + if (reg == NULL) + continue; + kw_i2c_add(host, np, child, *reg); + } + } + } } + /* * * PMU implementation @@ -403,7 +521,7 @@ */ -#ifdef CONFIG_ADB_PMU +#ifdef CONFIG_ADB_PMU__disabled static int pmu_low_i2c_func(struct low_i2c_host *host, u8 addr, u8 sub, u8 *data, int len) { @@ -431,104 +549,509 @@ #endif /* CONFIG_ADB_PMU */ -void __init pmac_init_low_i2c(void) -{ - struct device_node *np; +/* + * + * Core code + * + */ - /* Probe keywest-i2c busses */ - np = of_find_compatible_node(NULL, "i2c", "keywest-i2c"); - while(np) { - keywest_low_i2c_add(np); - np = of_find_compatible_node(np, "i2c", "keywest-i2c"); + +struct pmac_i2c_bus *pmac_i2c_find_bus(struct device_node *node) +{ + struct device_node *p = of_node_get(node); + struct device_node *prev = NULL; + struct pmac_i2c_bus *bus; + + while(p) { + list_for_each_entry(bus, &pmac_i2c_busses, link) { + if (p == bus->busnode) { + if (prev && bus->flags & pmac_i2c_multibus) { + u32 *reg; + reg = (u32 *)get_property(prev, "reg", + NULL); + if (!reg) + continue; + if (((*reg) >> 8) != bus->channel) + continue; + } + of_node_put(p); + of_node_put(prev); + return bus; + } + } + of_node_put(prev); + prev = p; + p = of_get_parent(p); } + return NULL; +} +EXPORT_SYMBOL_GPL(pmac_i2c_find_bus); -#ifdef CONFIG_ADB_PMU - /* Probe PMU busses */ - np = of_find_node_by_name(NULL, "via-pmu"); - if (np) - pmu_low_i2c_add(np); -#endif /* CONFIG_ADB_PMU */ +u8 pmac_i2c_get_dev_addr(struct device_node *device) +{ + u32 *reg = (u32 *)get_property(device, "reg", NULL); - /* TODO: Add CUDA support as well */ + if (reg == NULL) + return 0; + + return (*reg) & 0xff; } +EXPORT_SYMBOL_GPL(pmac_i2c_get_dev_addr); + +struct device_node *pmac_i2c_get_controller(struct pmac_i2c_bus *bus) +{ + return bus->controller; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_controller); + +struct device_node *pmac_i2c_get_bus_node(struct pmac_i2c_bus *bus) +{ + return bus->busnode; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_bus_node); + +int pmac_i2c_get_type(struct pmac_i2c_bus *bus) +{ + return bus->type; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_type); + +int pmac_i2c_get_flags(struct pmac_i2c_bus *bus) +{ + return bus->flags; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_flags); + +void pmac_i2c_attach_adapter(struct pmac_i2c_bus *bus, + struct i2c_adapter *adapter) +{ + WARN_ON(bus->adapter != NULL); + bus->adapter = adapter; +} +EXPORT_SYMBOL_GPL(pmac_i2c_attach_adapter); + +void pmac_i2c_detach_adapter(struct pmac_i2c_bus *bus, + struct i2c_adapter *adapter) +{ + WARN_ON(bus->adapter != adapter); + bus->adapter = NULL; +} +EXPORT_SYMBOL_GPL(pmac_i2c_detach_adapter); + +struct i2c_adapter *pmac_i2c_get_adapter(struct pmac_i2c_bus *bus) +{ + return bus->adapter; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_adapter); + +extern int pmac_i2c_match_adapter(struct device_node *dev, + struct i2c_adapter *adapter) +{ + struct pmac_i2c_bus *bus = pmac_i2c_find_bus(dev); + + if (bus == NULL) + return 0; + return (bus->adapter == adapter); +} +EXPORT_SYMBOL_GPL(pmac_i2c_match_adapter); int pmac_low_i2c_lock(struct device_node *np) { - struct low_i2c_host *host = find_low_i2c_host(np); + struct pmac_i2c_bus *bus, *found = NULL; - if (!host) + list_for_each_entry(bus, &pmac_i2c_busses, link) { + if (np == bus->controller) { + found = bus; + break; + } + } + if (!found) return -ENODEV; - down(&host->mutex); - return 0; + return pmac_i2c_open(bus, 0); } -EXPORT_SYMBOL(pmac_low_i2c_lock); +EXPORT_SYMBOL_GPL(pmac_low_i2c_lock); int pmac_low_i2c_unlock(struct device_node *np) { - struct low_i2c_host *host = find_low_i2c_host(np); + struct pmac_i2c_bus *bus, *found = NULL; - if (!host) + list_for_each_entry(bus, &pmac_i2c_busses, link) { + if (np == bus->controller) { + found = bus; + break; + } + } + if (!found) return -ENODEV; - up(&host->mutex); + pmac_i2c_close(bus); return 0; } -EXPORT_SYMBOL(pmac_low_i2c_unlock); +EXPORT_SYMBOL_GPL(pmac_low_i2c_unlock); -int pmac_low_i2c_open(struct device_node *np, int channel) +int pmac_i2c_open(struct pmac_i2c_bus *bus, int polled) { - struct low_i2c_host *host = find_low_i2c_host(np); + int rc; - if (!host) - return -ENODEV; + down(&bus->sem); + bus->polled = polled; + bus->opened = 1; + bus->mode = pmac_i2c_mode_std; + if (bus->open && (rc = bus->open(bus)) != 0) { + bus->opened = 0; + up(&bus->sem); + return rc; + } + return 0; +} +EXPORT_SYMBOL_GPL(pmac_i2c_open); - if (channel >= host->num_channels) - return -EINVAL; +void pmac_i2c_close(struct pmac_i2c_bus *bus) +{ + WARN_ON(!bus->opened); + if (bus->close) + bus->close(bus); + bus->opened = 0; + up(&bus->sem); +} +EXPORT_SYMBOL_GPL(pmac_i2c_close); - down(&host->mutex); - host->is_open = 1; - host->channel = channel; +int pmac_i2c_setmode(struct pmac_i2c_bus *bus, int mode) +{ + WARN_ON(!bus->opened); + bus->mode = mode; return 0; } -EXPORT_SYMBOL(pmac_low_i2c_open); +EXPORT_SYMBOL_GPL(pmac_i2c_setmode); -int pmac_low_i2c_close(struct device_node *np) +int pmac_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len) { - struct low_i2c_host *host = find_low_i2c_host(np); + int rc; - if (!host) - return -ENODEV; + WARN_ON(!bus->opened); - host->is_open = 0; - up(&host->mutex); + DBG("xfer() chan=%d, addrdir=0x%x, mode=%d, subsize=%d, subaddr=0x%x," + " %d bytes, bus %s\n", bus->channel, addrdir, bus->mode, subsize, + subaddr, len, bus->busnode->full_name); - return 0; + rc = bus->xfer(bus, addrdir, subsize, subaddr, data, len); + +#ifdef DEBUG + if (rc) + DBG("xfer error %d\n", rc); +#endif + return rc; } -EXPORT_SYMBOL(pmac_low_i2c_close); +EXPORT_SYMBOL_GPL(pmac_i2c_xfer); + +/* some quirks for platform function decoding */ +enum { + pmac_i2c_quirk_invmask = 0x00000001u, +}; -int pmac_low_i2c_setmode(struct device_node *np, int mode) +static void pmac_i2c_devscan(void (*callback)(struct device_node *dev, + int quirks)) { - struct low_i2c_host *host = find_low_i2c_host(np); + struct pmac_i2c_bus *bus; + struct device_node *np; + static struct whitelist_ent { + char *name; + char *compatible; + int quirks; + } whitelist[] = { + /* XXX Study device-tree's & apple drivers are get the quirks + * right ! + */ + { "i2c-hwclock", NULL, pmac_i2c_quirk_invmask }, + { "i2c-cpu-voltage", NULL, 0}, + { "temp-monitor", NULL, 0 }, + { "supply-monitor", NULL, 0 }, + { NULL, NULL, 0 }, + }; + + /* Only some devices need to have platform functions instanciated + * here. For now, we have a table. Others, like 9554 i2c GPIOs used + * on Xserve, if we ever do a driver for them, will use their own + * platform function instance + */ + list_for_each_entry(bus, &pmac_i2c_busses, link) { + for (np = NULL; + (np = of_get_next_child(bus->busnode, np)) != NULL;) { + struct whitelist_ent *p; + /* If multibus, check if device is on that bus */ + if (bus->flags & pmac_i2c_multibus) + if (bus != pmac_i2c_find_bus(np)) + continue; + for (p = whitelist; p->name != NULL; p++) { + if (strcmp(np->name, p->name)) + continue; + if (p->compatible && + !device_is_compatible(np, p->compatible)) + continue; + callback(np, p->quirks); + break; + } + } + } +} - if (!host) - return -ENODEV; - WARN_ON(!host->is_open); - host->mode = mode; +#define MAX_I2C_DATA 64 + +struct pmac_i2c_pf_inst +{ + struct pmac_i2c_bus *bus; + u8 addr; + u8 buffer[MAX_I2C_DATA]; + u8 scratch[MAX_I2C_DATA]; + int bytes; + int quirks; +}; + +static void* pmac_i2c_do_begin(struct pmf_function *func, struct pmf_args *args) +{ + struct pmac_i2c_pf_inst *inst; + struct pmac_i2c_bus *bus; + + bus = pmac_i2c_find_bus(func->node); + if (bus == NULL) { + printk(KERN_ERR "low_i2c: Can't find bus for %s (pfunc)\n", + func->node->full_name); + return NULL; + } + if (pmac_i2c_open(bus, 0)) { + printk(KERN_ERR "low_i2c: Can't open i2c bus for %s (pfunc)\n", + func->node->full_name); + return NULL; + } + + /* XXX might need GFP_ATOMIC when called during the suspend process, + * but then, there are already lots of issues with suspending when + * near OOM that need to be resolved, the allocator itself should + * probably make GFP_NOIO implicit during suspend + */ + inst = kzalloc(sizeof(struct pmac_i2c_pf_inst), GFP_KERNEL); + if (inst == NULL) { + pmac_i2c_close(bus); + return NULL; + } + inst->bus = bus; + inst->addr = pmac_i2c_get_dev_addr(func->node); + inst->quirks = (int)(long)func->driver_data; + return inst; +} +static void pmac_i2c_do_end(struct pmf_function *func, void *instdata) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + if (inst == NULL) + return; + pmac_i2c_close(inst->bus); + if (inst) + kfree(inst); +} + +static int pmac_i2c_do_read(PMF_STD_ARGS, u32 len) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + inst->bytes = len; + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_read, 0, 0, + inst->buffer, len); +} + +static int pmac_i2c_do_write(PMF_STD_ARGS, u32 len, const u8 *data) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_write, 0, 0, + (u8 *)data, len); +} + +/* This function is used to do the masking & OR'ing for the "rmw" type + * callbacks. Ze should apply the mask and OR in the values in the + * buffer before writing back. The problem is that it seems that + * various darwin drivers implement the mask/or differently, thus + * we need to check the quirks first + */ +static void pmac_i2c_do_apply_rmw(struct pmac_i2c_pf_inst *inst, + u32 len, const u8 *mask, const u8 *val) +{ + int i; + + if (inst->quirks & pmac_i2c_quirk_invmask) { + for (i = 0; i < len; i ++) + inst->scratch[i] = (inst->buffer[i] & mask[i]) | val[i]; + } else { + for (i = 0; i < len; i ++) + inst->scratch[i] = (inst->buffer[i] & ~mask[i]) + | (val[i] & mask[i]); + } +} + +static int pmac_i2c_do_rmw(PMF_STD_ARGS, u32 masklen, u32 valuelen, + u32 totallen, const u8 *maskdata, + const u8 *valuedata) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + if (masklen > inst->bytes || valuelen > inst->bytes || + totallen > inst->bytes || valuelen > masklen) + return -EINVAL; + + pmac_i2c_do_apply_rmw(inst, masklen, maskdata, valuedata); + + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_write, 0, 0, + inst->scratch, totallen); +} + +static int pmac_i2c_do_read_sub(PMF_STD_ARGS, u8 subaddr, u32 len) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + inst->bytes = len; + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_read, 1, subaddr, + inst->buffer, len); +} + +static int pmac_i2c_do_write_sub(PMF_STD_ARGS, u8 subaddr, u32 len, + const u8 *data) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_write, 1, + subaddr, (u8 *)data, len); +} + +static int pmac_i2c_do_set_mode(PMF_STD_ARGS, int mode) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + return pmac_i2c_setmode(inst->bus, mode); +} + +static int pmac_i2c_do_rmw_sub(PMF_STD_ARGS, u8 subaddr, u32 masklen, + u32 valuelen, u32 totallen, const u8 *maskdata, + const u8 *valuedata) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + if (masklen > inst->bytes || valuelen > inst->bytes || + totallen > inst->bytes || valuelen > masklen) + return -EINVAL; + + pmac_i2c_do_apply_rmw(inst, masklen, maskdata, valuedata); + + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_write, 1, + subaddr, inst->scratch, totallen); +} + +static int pmac_i2c_do_mask_and_comp(PMF_STD_ARGS, u32 len, + const u8 *maskdata, + const u8 *valuedata) +{ + struct pmac_i2c_pf_inst *inst = instdata; + int i, match; + + /* Get return value pointer, it's assumed to be a u32 */ + if (!args || !args->count || !args->u[0].p) + return -EINVAL; + + /* Check buffer */ + if (len > inst->bytes) + return -EINVAL; + + for (i = 0, match = 1; match && i < len; i ++) + if ((inst->buffer[i] & maskdata[i]) != valuedata[i]) + match = 0; + *args->u[0].p = match; return 0; } -EXPORT_SYMBOL(pmac_low_i2c_setmode); -int pmac_low_i2c_xfer(struct device_node *np, u8 addrdir, u8 subaddr, u8 *data, int len) +static int pmac_i2c_do_delay(PMF_STD_ARGS, u32 duration) { - struct low_i2c_host *host = find_low_i2c_host(np); + msleep((duration + 999) / 1000); + return 0; +} - if (!host) - return -ENODEV; - WARN_ON(!host->is_open); - return host->func(host, addrdir, subaddr, data, len); +static struct pmf_handlers pmac_i2c_pfunc_handlers = { + .begin = pmac_i2c_do_begin, + .end = pmac_i2c_do_end, + .read_i2c = pmac_i2c_do_read, + .write_i2c = pmac_i2c_do_write, + .rmw_i2c = pmac_i2c_do_rmw, + .read_i2c_sub = pmac_i2c_do_read_sub, + .write_i2c_sub = pmac_i2c_do_write_sub, + .rmw_i2c_sub = pmac_i2c_do_rmw_sub, + .set_i2c_mode = pmac_i2c_do_set_mode, + .mask_and_compare = pmac_i2c_do_mask_and_comp, + .delay = pmac_i2c_do_delay, +}; + + +static void __init pmac_i2c_dev_init(struct device_node *np, int quirks) +{ + DBG("low_i2c: dev_init(%s)\n", np->full_name); + + pmf_register_driver(np, &pmac_i2c_pfunc_handlers, + (void *)(long)quirks); + pmf_do_functions(np, NULL, 0, PMF_FLAGS_ON_INIT, NULL); +} + +static void pmac_i2c_dev_suspend(struct device_node *np, int quirks) +{ + DBG("low_i2c: dev_suspend(%s)\n", np->full_name); + pmf_do_functions(np, NULL, 0, PMF_FLAGS_ON_SLEEP, NULL); +} + +static void pmac_i2c_dev_resume(struct device_node *np, int quirks) +{ + DBG("low_i2c: dev_resume(%s)\n", np->full_name); + pmf_do_functions(np, NULL, 0, PMF_FLAGS_ON_WAKE, NULL); +} + +void pmac_pfunc_i2c_suspend(void) +{ + pmac_i2c_devscan(pmac_i2c_dev_suspend); +} + +void pmac_pfunc_i2c_resume(void) +{ + pmac_i2c_devscan(pmac_i2c_dev_resume); +} + +/* + * Initialize us: probe all i2c busses on the machine, instantiate + * busses and platform functions as needed. + */ +/* This is non-static as it might be called early by smp code */ +int __init pmac_i2c_init(void) +{ + static int i2c_inited; + + if (i2c_inited) + return 0; + i2c_inited = 1; + + /* Probe keywest-i2c busses */ + kw_i2c_probe(); + +#ifdef CONFIG_ADB_PMU__disabled + /* Probe PMU busses */ + np = of_find_node_by_name(NULL, "via-pmu"); + if (np) + pmu_low_i2c_add(np); +#endif /* CONFIG_ADB_PMU */ + + /* TODO: Add others... */ + + /* Now add plaform functions for some known devices */ + pmac_i2c_devscan(pmac_i2c_dev_init); + + return 0; } -EXPORT_SYMBOL(pmac_low_i2c_xfer); +arch_initcall(pmac_i2c_init); Index: linux-work/include/asm-powerpc/pmac_low_i2c.h =================================================================== --- linux-work.orig/include/asm-powerpc/pmac_low_i2c.h 2005-12-21 10:24:46.000000000 +1100 +++ linux-work/include/asm-powerpc/pmac_low_i2c.h 2006-01-04 16:53:13.000000000 +1100 @@ -15,31 +15,91 @@ /* i2c mode (based on the platform functions format) */ enum { - pmac_low_i2c_mode_dumb = 1, - pmac_low_i2c_mode_std = 2, - pmac_low_i2c_mode_stdsub = 3, - pmac_low_i2c_mode_combined = 4, + pmac_i2c_mode_dumb = 1, + pmac_i2c_mode_std = 2, + pmac_i2c_mode_stdsub = 3, + pmac_i2c_mode_combined = 4, }; /* RW bit in address */ enum { - pmac_low_i2c_read = 0x01, - pmac_low_i2c_write = 0x00 + pmac_i2c_read = 0x01, + pmac_i2c_write = 0x00 }; +/* i2c bus type */ +enum { + pmac_i2c_bus_keywest = 0, + pmac_i2c_bus_pmu = 1, + pmac_i2c_bus_smu = 2, +}; + +/* i2c bus features */ +enum { + /* can_largesub : supports >1 byte subaddresses (SMU only) */ + pmac_i2c_can_largesub = 0x00000001u, + + /* multibus : device node holds multiple busses, bus number is + * encoded in bits 0xff00 of "reg" of a given device + */ + pmac_i2c_multibus = 0x00000002u, +}; + +/* i2c busses in the system */ +struct pmac_i2c_bus; +struct i2c_adapter; + /* Init, called early during boot */ -extern void pmac_init_low_i2c(void); +extern int pmac_i2c_init(void); -/* Locking functions exposed to i2c-keywest */ -int pmac_low_i2c_lock(struct device_node *np); -int pmac_low_i2c_unlock(struct device_node *np); +/* Lookup an i2c bus for a device-node. The node can be either the bus + * node itself or a device below it. In the case of a multibus, the bus + * node itself is the controller node, else, it's a child of the controller + * node + */ +extern struct pmac_i2c_bus *pmac_i2c_find_bus(struct device_node *node); + +/* Get the address for an i2c device. This strips the bus number if + * necessary. The 7 bits address is returned 1 bit right shifted so that the + * direction can be directly ored in + */ +extern u8 pmac_i2c_get_dev_addr(struct device_node *device); + +/* Get infos about a bus */ +extern struct device_node *pmac_i2c_get_controller(struct pmac_i2c_bus *bus); +extern struct device_node *pmac_i2c_get_bus_node(struct pmac_i2c_bus *bus); +extern int pmac_i2c_get_type(struct pmac_i2c_bus *bus); +extern int pmac_i2c_get_flags(struct pmac_i2c_bus *bus); + +/* i2c layer adapter attach/detach */ +extern void pmac_i2c_attach_adapter(struct pmac_i2c_bus *bus, + struct i2c_adapter *adapter); +extern void pmac_i2c_detach_adapter(struct pmac_i2c_bus *bus, + struct i2c_adapter *adapter); +extern struct i2c_adapter *pmac_i2c_get_adapter(struct pmac_i2c_bus *bus); + +/* March a device or bus with an i2c adapter structure, to be used by drivers + * to match device-tree nodes with i2c adapters during adapter discovery + * callbacks + */ +extern int pmac_i2c_match_adapter(struct device_node *dev, + struct i2c_adapter *adapter); + + +/* (legacy) Locking functions exposed to i2c-keywest */ +extern int pmac_low_i2c_lock(struct device_node *np); +extern int pmac_low_i2c_unlock(struct device_node *np); /* Access functions for platform code */ -int pmac_low_i2c_open(struct device_node *np, int channel); -int pmac_low_i2c_close(struct device_node *np); -int pmac_low_i2c_setmode(struct device_node *np, int mode); -int pmac_low_i2c_xfer(struct device_node *np, u8 addrdir, u8 subaddr, u8 *data, int len); - +extern int pmac_i2c_open(struct pmac_i2c_bus *bus, int polled); +extern void pmac_i2c_close(struct pmac_i2c_bus *bus); +extern int pmac_i2c_setmode(struct pmac_i2c_bus *bus, int mode); +extern int pmac_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len); + +/* Suspend/resume code called by via-pmu directly for now */ +extern void pmac_pfunc_i2c_suspend(void); +extern void pmac_pfunc_i2c_resume(void); #endif /* __KERNEL__ */ #endif /* __PMAC_LOW_I2C_H__ */ Index: linux-work/drivers/macintosh/via-pmu.c =================================================================== --- linux-work.orig/drivers/macintosh/via-pmu.c 2005-12-19 16:13:42.000000000 +1100 +++ linux-work/drivers/macintosh/via-pmu.c 2006-01-04 16:53:54.000000000 +1100 @@ -2358,8 +2358,9 @@ return -EBUSY; } - /* Disable clock spreading on some machines */ - pmac_tweak_clock_spreading(0); + /* Call platform functions marked "on sleep" */ + pmac_pfunc_i2c_suspend(; + pmac_pfunc_base_suspend(); /* Stop preemption */ preempt_disable(); @@ -2431,8 +2432,9 @@ mdelay(10); preempt_enable(); - /* Re-enable clock spreading on some machines */ - pmac_tweak_clock_spreading(1); + /* Call platform functions marked "on wake" */ + pmac_pfunc_base_resume(); + pmac_pfunc_i2c_resume(); /* Resume devices */ device_resume(); From benh at kernel.crashing.org Thu Jan 5 16:47:18 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Thu, 05 Jan 2006 16:47:18 +1100 Subject: [PATCH] powerpc: Add cpufreq for various G5 machines Message-ID: <1136440038.4840.55.camel@localhost.localdomain> This patch uses the platform function patch posted earlier to implement cpu frequency switching on various G5 models. For now, this should work with all PowerMac7,2 and 7,3 (in addition to the code that was already there for iMac G5 and PowerMac9,1), that is desktop modeles with an AGP slot (not the newest PCI Express ones yet, that will be next). I tested it on the dual 2.5 Ghz here (pulsar clock chip) but I haven't had a chance to test it on other models using other frequencies or other clock chips. It's also not hooked to the thermal control code at all yet, this will happen once I port therm_pm72 to the windfarm architecture. I didn't found a nice & clear way to "know" what the low frequency value is (I only know how to tell the clock chip to slew to "low speed", not what "low speed" really is). I think I found a way to figure it out, but it might not work on all models, thus you might end up with a slightly incorrect value when running low speed in /proc/cpuinfo, though that should have no other effect since the kernel doesn't rely on the CPU core frequency for it's timekeeping. If you want to verify, you can use the little hack at http://gate.crashing.org/~benh/cpufreq_g5.c which attempts to "measure" the cpu frequency (970 only). Signed-off-by: Benjamin Herrenschmidt Index: linux-work/arch/powerpc/platforms/powermac/cpufreq_64.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/cpufreq_64.c 2006-01-05 15:11:16.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/cpufreq_64.c 2006-01-05 16:18:23.000000000 +1100 @@ -28,6 +28,7 @@ #include #include #include +#include #undef DEBUG @@ -85,6 +86,10 @@ static int g5_pmode_max; static int g5_pmode_cur; +static void (*g5_switch_volt)(int speed_mode); +static int (*g5_switch_freq)(int speed_mode); +static int (*g5_query_freq)(void); + static DECLARE_MUTEX(g5_switch_mutex); @@ -92,9 +97,12 @@ static int g5_fvt_count; /* number of op. points */ static int g5_fvt_cur; /* current op. point */ -/* ----------------- real hardware interface */ -static void g5_switch_volt(int speed_mode) +/* + * SMU based voltage switching for Neo2 platforms + */ + +static void g5_smu_switch_volt(int speed_mode) { struct smu_simple_cmd cmd; @@ -105,26 +113,20 @@ wait_for_completion(&comp); } -static int g5_switch_freq(int speed_mode) +/* + * SCOM based frequency switching for 970FX rev3 + */ +static int g5_scom_switch_freq(int speed_mode) { - struct cpufreq_freqs freqs; + unsigned long flags; int to; - if (g5_pmode_cur == speed_mode) - return 0; - - down(&g5_switch_mutex); - - freqs.old = g5_cpu_freqs[g5_pmode_cur].frequency; - freqs.new = g5_cpu_freqs[speed_mode].frequency; - freqs.cpu = 0; - - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - /* If frequency is going up, first ramp up the voltage */ if (speed_mode < g5_pmode_cur) g5_switch_volt(speed_mode); + local_irq_save(flags); + /* Clear PCR high */ scom970_write(SCOM_PCR, 0); /* Clear PCR low */ @@ -147,6 +149,8 @@ udelay(100); } + local_irq_restore(flags); + /* If frequency is going down, last ramp the voltage */ if (speed_mode > g5_pmode_cur) g5_switch_volt(speed_mode); @@ -154,14 +158,10 @@ g5_pmode_cur = speed_mode; ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - up(&g5_switch_mutex); - return 0; } -static int g5_query_freq(void) +static int g5_scom_query_freq(void) { unsigned long psr = scom970_read(SCOM_PSR); int i; @@ -173,7 +173,97 @@ return i; } -/* ----------------- cpufreq bookkeeping */ +/* + * Platform function based voltage switching for PowerMac7,2 & 7,3 + */ + +static struct pmf_function *pfunc_cpu0_volt_high; +static struct pmf_function *pfunc_cpu0_volt_low; +static struct pmf_function *pfunc_cpu1_volt_high; +static struct pmf_function *pfunc_cpu1_volt_low; + +static void g5_pfunc_switch_volt(int speed_mode) +{ + if (speed_mode == CPUFREQ_HIGH) { + if (pfunc_cpu0_volt_high) + pmf_call_one(pfunc_cpu0_volt_high, NULL); + if (pfunc_cpu1_volt_high) + pmf_call_one(pfunc_cpu1_volt_high, NULL); + } else { + if (pfunc_cpu0_volt_low) + pmf_call_one(pfunc_cpu0_volt_low, NULL); + if (pfunc_cpu1_volt_low) + pmf_call_one(pfunc_cpu1_volt_low, NULL); + } + msleep(10); /* should be faster , to fix */ +} + +/* + * Platform function based frequency switching for PowerMac7,2 & 7,3 + */ + +static struct pmf_function *pfunc_cpu_setfreq_high; +static struct pmf_function *pfunc_cpu_setfreq_low; +static struct pmf_function *pfunc_cpu_getfreq; +static struct pmf_function *pfunc_slewing_done;; + +static int g5_pfunc_switch_freq(int speed_mode) +{ + struct pmf_args args; + u32 done = 0; + unsigned long timeout; + + /* If frequency is going up, first ramp up the voltage */ + if (speed_mode < g5_pmode_cur) + g5_switch_volt(speed_mode); + + /* Do it */ + if (speed_mode == CPUFREQ_HIGH) + pmf_call_one(pfunc_cpu_setfreq_high, NULL); + else + pmf_call_one(pfunc_cpu_setfreq_low, NULL); + + /* It's an irq GPIO so we should be able to just block here, + * I'll do that later after I've properly tested the IRQ code for + * platform functions + */ + timeout = jiffies + HZ/10; + while(!time_after(jiffies, timeout)) { + done = 0; + args.count = 1; + args.u[0].p = &done; + pmf_call_one(pfunc_slewing_done, &args); + if (done) + break; + msleep(1); + } + if (done == 0) + printk(KERN_WARNING "cpufreq: Timeout in clock slewing !\n"); + + /* If frequency is going down, last ramp the voltage */ + if (speed_mode > g5_pmode_cur) + g5_switch_volt(speed_mode); + + g5_pmode_cur = speed_mode; + ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul; + + return 0; +} + +static int g5_pfunc_query_freq(void) +{ + struct pmf_args args; + u32 val = 0; + + args.count = 1; + args.u[0].p = &val; + pmf_call_one(pfunc_cpu_getfreq, &args); + return val ? CPUFREQ_HIGH : CPUFREQ_LOW; +} + +/* + * Common interface to the cpufreq core + */ static int g5_cpufreq_verify(struct cpufreq_policy *policy) { @@ -183,13 +273,30 @@ static int g5_cpufreq_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation) { - unsigned int newstate = 0; + unsigned int newstate = 0; + struct cpufreq_freqs freqs; + int rc; if (cpufreq_frequency_table_target(policy, g5_cpu_freqs, target_freq, relation, &newstate)) return -EINVAL; - return g5_switch_freq(newstate); + if (g5_pmode_cur == newstate) + return 0; + + down(&g5_switch_mutex); + + freqs.old = g5_cpu_freqs[g5_pmode_cur].frequency; + freqs.new = g5_cpu_freqs[newstate].frequency; + freqs.cpu = 0; + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + rc = g5_switch_freq(newstate); + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + up(&g5_switch_mutex); + + return rc; } static unsigned int g5_cpufreq_get_speed(unsigned int cpu) @@ -205,6 +312,7 @@ policy->governor = CPUFREQ_DEFAULT_GOVERNOR; policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; policy->cur = g5_cpu_freqs[g5_query_freq()].frequency; + policy->cpus = cpu_possible_map; cpufreq_frequency_table_get_attr(g5_cpu_freqs, policy->cpu); return cpufreq_frequency_table_cpuinfo(policy, @@ -224,19 +332,24 @@ }; -static int __init g5_cpufreq_init(void) +static int __init g5_neo2_cpufreq_init(struct device_node *cpus) { struct device_node *cpunode; unsigned int psize, ssize; struct smu_sdbp_header *shdr; unsigned long max_freq; + char *freq_method, *volt_method; u32 *valp; int rc = -ENODEV; - /* Look for CPU and SMU nodes */ - cpunode = of_find_node_by_type(NULL, "cpu"); - if (!cpunode) { - DBG("No CPU node !\n"); + /* Get first CPU node */ + for (cpunode = NULL; + (cpunode = of_get_next_child(cpus, cpunode)) != NULL;) { + if (!strcmp(cpunode->type, "cpu")) + break; + } + if (cpunode == NULL) { + printk(KERN_ERR "cpufreq: Can't find any CPU node\n"); return -ENODEV; } @@ -286,19 +399,23 @@ g5_cpu_freqs[0].frequency = max_freq; g5_cpu_freqs[1].frequency = max_freq/2; - /* Check current frequency */ - g5_pmode_cur = g5_query_freq(); - if (g5_pmode_cur > 1) - /* We don't support anything but 1:1 and 1:2, fixup ... */ - g5_pmode_cur = 1; + /* Set callbacks */ + g5_switch_volt = g5_smu_switch_volt; + g5_switch_freq = g5_scom_switch_freq; + g5_query_freq = g5_scom_query_freq; + freq_method = "SCOM"; + volt_method = "SMU"; /* Force apply current frequency to make sure everything is in * sync (voltage is right for example). Firmware may leave us with * a strange setting ... */ - g5_switch_freq(g5_pmode_cur); + g5_pmode_cur = -1; + g5_switch_freq(g5_query_freq()); printk(KERN_INFO "Registering G5 CPU frequency driver\n"); + printk(KERN_INFO "Frequency method: %s, Voltage method: %s\n", + freq_method, volt_method); printk(KERN_INFO "Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n", g5_cpu_freqs[1].frequency/1000, g5_cpu_freqs[0].frequency/1000, @@ -317,6 +434,199 @@ return rc; } +static int __init g5_pm72_cpufreq_init(struct device_node *cpus) +{ + struct device_node *cpuid = NULL, *hwclock = NULL, *cpunode = NULL; + u8 *eeprom = NULL; + u32 *valp; + u64 max_freq, min_freq, ih, il; + int has_volt = 1, rc = 0; + + /* Get first CPU node */ + for (cpunode = NULL; + (cpunode = of_get_next_child(cpus, cpunode)) != NULL;) { + if (!strcmp(cpunode->type, "cpu")) + break; + } + if (cpunode == NULL) { + printk(KERN_ERR "cpufreq: Can't find any CPU node\n"); + return -ENODEV; + } + + /* Lookup the cpuid eeprom node */ + cpuid = of_find_node_by_path("/u3 at 0,f8000000/i2c at f8001000/cpuid at a0"); + if (cpuid != NULL) + eeprom = (u8 *)get_property(cpuid, "cpuid", NULL); + if (eeprom == NULL) { + printk(KERN_ERR "cpufreq: Can't find cpuid EEPROM !\n"); + rc = -ENODEV; + goto bail; + } + + /* Lookup the i2c hwclock */ + for (hwclock = NULL; + (hwclock = of_find_node_by_name(hwclock, "i2c-hwclock")) != NULL;){ + char *loc = get_property(hwclock, "hwctrl-location", NULL); + if (loc == NULL) + continue; + if (strcmp(loc, "CPU CLOCK")) + continue; + if (!get_property(hwclock, "platform-get-frequency", NULL)) + continue; + break; + } + if (hwclock == NULL) { + printk(KERN_ERR "cpufreq: Can't find i2c clock chip !\n"); + rc = -ENODEV; + goto bail; + } + + DBG("cpufreq: i2c clock chip found: %s\n", hwclock->full_name); + + /* Now get all the platform functions */ + pfunc_cpu_getfreq = + pmf_find_function(hwclock, "get-frequency"); + pfunc_cpu_setfreq_high = + pmf_find_function(hwclock, "set-frequency-high"); + pfunc_cpu_setfreq_low = + pmf_find_function(hwclock, "set-frequency-low"); + pfunc_slewing_done = + pmf_find_function(hwclock, "slewing-done"); + pfunc_cpu0_volt_high = + pmf_find_function(hwclock, "set-voltage-high-0"); + pfunc_cpu0_volt_low = + pmf_find_function(hwclock, "set-voltage-low-0"); + pfunc_cpu1_volt_high = + pmf_find_function(hwclock, "set-voltage-high-1"); + pfunc_cpu1_volt_low = + pmf_find_function(hwclock, "set-voltage-low-1"); + + /* Check we have minimum requirements */ + if (pfunc_cpu_getfreq == NULL || pfunc_cpu_setfreq_high == NULL || + pfunc_cpu_setfreq_low == NULL || pfunc_slewing_done == NULL) { + printk(KERN_ERR "cpufreq: Can't find platform functions !\n"); + rc = -ENODEV; + goto bail; + } + + /* Check that we have complete sets */ + if (pfunc_cpu0_volt_high == NULL || pfunc_cpu0_volt_low == NULL) { + pmf_put_function(pfunc_cpu0_volt_high); + pmf_put_function(pfunc_cpu0_volt_low); + pfunc_cpu0_volt_high = pfunc_cpu0_volt_low = NULL; + has_volt = 0; + } + if (!has_volt || + pfunc_cpu1_volt_high == NULL || pfunc_cpu1_volt_low == NULL) { + pmf_put_function(pfunc_cpu1_volt_high); + pmf_put_function(pfunc_cpu1_volt_low); + pfunc_cpu1_volt_high = pfunc_cpu1_volt_low = NULL; + } + + /* Note: The device tree also contains a "platform-set-values" + * function for which I haven't quite figured out the usage. It + * might have to be called on init and/or wakeup, I'm not too sure + * but things seem to work fine without it so far ... + */ + + /* Get max frequency from device-tree */ + valp = (u32 *)get_property(cpunode, "clock-frequency", NULL); + if (!valp) { + printk(KERN_ERR "cpufreq: Can't find CPU frequency !\n"); + rc = -ENODEV; + goto bail; + } + + max_freq = (*valp)/1000; + + /* Now calculate reduced frequency by using the cpuid input freq + * ratio. This requires 64 bits math unless we are willing to lose + * some precision + */ + + ih = *((u32 *)(eeprom + 0x10)); + il = *((u32 *)(eeprom + 0x20)); + min_freq = 0; + if (ih != 0 && il != 0) + min_freq = (max_freq * il) / ih; + + /* Sanity check */ + if (min_freq >= max_freq || min_freq < 1000) { + printk(KERN_ERR "cpufreq: Can't calculate low frequency !\n"); + rc = -ENODEV; + goto bail; + } + g5_cpu_freqs[0].frequency = max_freq; + g5_cpu_freqs[1].frequency = min_freq; + + /* Set callbacks */ + g5_switch_volt = g5_pfunc_switch_volt; + g5_switch_freq = g5_pfunc_switch_freq; + g5_query_freq = g5_pfunc_query_freq; + + /* Force apply current frequency to make sure everything is in + * sync (voltage is right for example). Firmware may leave us with + * a strange setting ... + */ + g5_pmode_cur = -1; + g5_switch_freq(g5_query_freq()); + + printk(KERN_INFO "Registering G5 CPU frequency driver\n"); + printk(KERN_INFO "Frequency method: i2c/pfunc, " + "Voltage method: %s\n", has_volt ? "i2c/pfunc" : "none"); + printk(KERN_INFO "Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n", + g5_cpu_freqs[1].frequency/1000, + g5_cpu_freqs[0].frequency/1000, + g5_cpu_freqs[g5_pmode_cur].frequency/1000); + + rc = cpufreq_register_driver(&g5_cpufreq_driver); + bail: + if (rc != 0) { + pmf_put_function(pfunc_cpu_getfreq); + pmf_put_function(pfunc_cpu_setfreq_high); + pmf_put_function(pfunc_cpu_setfreq_low); + pmf_put_function(pfunc_slewing_done); + pmf_put_function(pfunc_cpu0_volt_high); + pmf_put_function(pfunc_cpu0_volt_low); + pmf_put_function(pfunc_cpu1_volt_high); + pmf_put_function(pfunc_cpu1_volt_low); + } + of_node_put(hwclock); + of_node_put(cpuid); + of_node_put(cpunode); + + return rc; +} + +static int __init g5_rm31_cpufreq_init(struct device_node *cpus) +{ + /* NYI */ + return 0; +} + +static int __init g5_cpufreq_init(void) +{ + struct device_node *cpus; + int rc; + + cpus = of_find_node_by_path("/cpus"); + if (cpus == NULL) { + DBG("No /cpus node !\n"); + return -ENODEV; + } + + if (machine_is_compatible("PowerMac7,2") || + machine_is_compatible("PowerMac7,3")) + rc = g5_pm72_cpufreq_init(cpus); + else if (machine_is_compatible("RackMac3,1")) + rc = g5_rm31_cpufreq_init(cpus); + else + rc = g5_neo2_cpufreq_init(cpus); + + of_node_put(cpus); + return rc; +} + module_init(g5_cpufreq_init); From benh at kernel.crashing.org Thu Jan 5 18:03:14 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Thu, 05 Jan 2006 18:03:14 +1100 Subject: [PATCH] powerpc: Add cpufreq for various G5 machines (#2) Message-ID: <1136444594.4840.69.camel@localhost.localdomain> This patch uses the platform function patch posted earlier to implement cpu frequency switching on various G5 models. For now, this should work with all PowerMac7,2 and 7,3 (in addition to the code that was already there for iMac G5 and PowerMac9,1), that is desktop modeles with an AGP slot and the desktop PCI Express ones (like the Quad G5). At this point, the only models still not supported are the iMac G5 iSight (PowerMac12,1) and the Xserve G5 (RackMac3,1) I tested it on the dual 2.5 Ghz here (pulsar clock chip) and the Quad g5 but I haven't had a chance to test it on other models using other frequencies or other clock chips. It's also not hooked to the thermal control code at all yet, this will happen once I port therm_pm72 to the windfarm architecture. I didn't found a nice & clear way to "know" what the low frequency value is on the PowerMac7,2 and 7,3 models. (I only know how to tell the clock chip to slew to "low speed", not what "low speed" really is). I think I found a way to figure it out, but it might not work on all models, thus you might end up with a slightly incorrect value when running low speed in /proc/cpuinfo, though that should have no other effect since the kernel doesn't rely on the CPU core frequency for it's timekeeping. If you want to verify, you can use the little hack at http://gate.crashing.org/~benh/cpufreq_g5.c which attempts to "measure" the cpu frequency (970 only). Signed-off-by: Benjamin Herrenschmidt Index: linux-work/arch/powerpc/platforms/powermac/cpufreq_64.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/cpufreq_64.c 2006-01-05 15:11:16.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/cpufreq_64.c 2006-01-05 17:49:33.000000000 +1100 @@ -28,6 +28,7 @@ #include #include #include +#include #undef DEBUG @@ -85,6 +86,10 @@ static int g5_pmode_max; static int g5_pmode_cur; +static void (*g5_switch_volt)(int speed_mode); +static int (*g5_switch_freq)(int speed_mode); +static int (*g5_query_freq)(void); + static DECLARE_MUTEX(g5_switch_mutex); @@ -92,9 +97,11 @@ static int g5_fvt_count; /* number of op. points */ static int g5_fvt_cur; /* current op. point */ -/* ----------------- real hardware interface */ +/* + * SMU based voltage switching for Neo2 platforms + */ -static void g5_switch_volt(int speed_mode) +static void g5_smu_switch_volt(int speed_mode) { struct smu_simple_cmd cmd; @@ -105,26 +112,57 @@ wait_for_completion(&comp); } -static int g5_switch_freq(int speed_mode) -{ - struct cpufreq_freqs freqs; - int to; +/* + * Platform function based voltage/vdnap switching for Neo2 + */ - if (g5_pmode_cur == speed_mode) - return 0; +static struct pmf_function *pfunc_set_vdnap0; +static struct pmf_function *pfunc_vdnap0_complete; - down(&g5_switch_mutex); +static void g5_vdnap_switch_volt(int speed_mode) +{ + struct pmf_args args; + u32 slew, done = 0; + unsigned long timeout; + + slew = (speed_mode == CPUFREQ_LOW) ? 1 : 0; + args.count = 1; + args.u[0].p = &slew; + + pmf_call_one(pfunc_set_vdnap0, &args); + + /* It's an irq GPIO so we should be able to just block here, + * I'll do that later after I've properly tested the IRQ code for + * platform functions + */ + timeout = jiffies + HZ/10; + while(!time_after(jiffies, timeout)) { + args.count = 1; + args.u[0].p = &done; + pmf_call_one(pfunc_vdnap0_complete, &args); + if (done) + break; + msleep(1); + } + if (done == 0) + printk(KERN_WARNING "cpufreq: Timeout in clock slewing !\n"); +} - freqs.old = g5_cpu_freqs[g5_pmode_cur].frequency; - freqs.new = g5_cpu_freqs[speed_mode].frequency; - freqs.cpu = 0; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); +/* + * SCOM based frequency switching for 970FX rev3 + */ +static int g5_scom_switch_freq(int speed_mode) +{ + unsigned long flags; + int to; /* If frequency is going up, first ramp up the voltage */ if (speed_mode < g5_pmode_cur) g5_switch_volt(speed_mode); + local_irq_save(flags); + /* Clear PCR high */ scom970_write(SCOM_PCR, 0); /* Clear PCR low */ @@ -147,6 +185,8 @@ udelay(100); } + local_irq_restore(flags); + /* If frequency is going down, last ramp the voltage */ if (speed_mode > g5_pmode_cur) g5_switch_volt(speed_mode); @@ -154,14 +194,10 @@ g5_pmode_cur = speed_mode; ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - up(&g5_switch_mutex); - return 0; } -static int g5_query_freq(void) +static int g5_scom_query_freq(void) { unsigned long psr = scom970_read(SCOM_PSR); int i; @@ -173,7 +209,104 @@ return i; } -/* ----------------- cpufreq bookkeeping */ +/* + * Platform function based voltage switching for PowerMac7,2 & 7,3 + */ + +static struct pmf_function *pfunc_cpu0_volt_high; +static struct pmf_function *pfunc_cpu0_volt_low; +static struct pmf_function *pfunc_cpu1_volt_high; +static struct pmf_function *pfunc_cpu1_volt_low; + +static void g5_pfunc_switch_volt(int speed_mode) +{ + if (speed_mode == CPUFREQ_HIGH) { + if (pfunc_cpu0_volt_high) + pmf_call_one(pfunc_cpu0_volt_high, NULL); + if (pfunc_cpu1_volt_high) + pmf_call_one(pfunc_cpu1_volt_high, NULL); + } else { + if (pfunc_cpu0_volt_low) + pmf_call_one(pfunc_cpu0_volt_low, NULL); + if (pfunc_cpu1_volt_low) + pmf_call_one(pfunc_cpu1_volt_low, NULL); + } + msleep(10); /* should be faster , to fix */ +} + +/* + * Platform function based frequency switching for PowerMac7,2 & 7,3 + */ + +static struct pmf_function *pfunc_cpu_setfreq_high; +static struct pmf_function *pfunc_cpu_setfreq_low; +static struct pmf_function *pfunc_cpu_getfreq; +static struct pmf_function *pfunc_slewing_done;; + +static int g5_pfunc_switch_freq(int speed_mode) +{ + struct pmf_args args; + u32 done = 0; + unsigned long timeout; + + /* If frequency is going up, first ramp up the voltage */ + if (speed_mode < g5_pmode_cur) + g5_switch_volt(speed_mode); + + /* Do it */ + if (speed_mode == CPUFREQ_HIGH) + pmf_call_one(pfunc_cpu_setfreq_high, NULL); + else + pmf_call_one(pfunc_cpu_setfreq_low, NULL); + + /* It's an irq GPIO so we should be able to just block here, + * I'll do that later after I've properly tested the IRQ code for + * platform functions + */ + timeout = jiffies + HZ/10; + while(!time_after(jiffies, timeout)) { + args.count = 1; + args.u[0].p = &done; + pmf_call_one(pfunc_slewing_done, &args); + if (done) + break; + msleep(1); + } + if (done == 0) + printk(KERN_WARNING "cpufreq: Timeout in clock slewing !\n"); + + /* If frequency is going down, last ramp the voltage */ + if (speed_mode > g5_pmode_cur) + g5_switch_volt(speed_mode); + + g5_pmode_cur = speed_mode; + ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul; + + return 0; +} + +static int g5_pfunc_query_freq(void) +{ + struct pmf_args args; + u32 val = 0; + + args.count = 1; + args.u[0].p = &val; + pmf_call_one(pfunc_cpu_getfreq, &args); + return val ? CPUFREQ_HIGH : CPUFREQ_LOW; +} + +/* + * Fake voltage switching for platforms with missing support + */ + +static void g5_dummy_switch_volt(int speed_mode) +{ +} + +/* + * Common interface to the cpufreq core + */ static int g5_cpufreq_verify(struct cpufreq_policy *policy) { @@ -183,13 +316,30 @@ static int g5_cpufreq_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation) { - unsigned int newstate = 0; + unsigned int newstate = 0; + struct cpufreq_freqs freqs; + int rc; if (cpufreq_frequency_table_target(policy, g5_cpu_freqs, target_freq, relation, &newstate)) return -EINVAL; - return g5_switch_freq(newstate); + if (g5_pmode_cur == newstate) + return 0; + + down(&g5_switch_mutex); + + freqs.old = g5_cpu_freqs[g5_pmode_cur].frequency; + freqs.new = g5_cpu_freqs[newstate].frequency; + freqs.cpu = 0; + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + rc = g5_switch_freq(newstate); + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + up(&g5_switch_mutex); + + return rc; } static unsigned int g5_cpufreq_get_speed(unsigned int cpu) @@ -205,6 +355,7 @@ policy->governor = CPUFREQ_DEFAULT_GOVERNOR; policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; policy->cur = g5_cpu_freqs[g5_query_freq()].frequency; + policy->cpus = cpu_possible_map; cpufreq_frequency_table_get_attr(g5_cpu_freqs, policy->cpu); return cpufreq_frequency_table_cpuinfo(policy, @@ -224,19 +375,39 @@ }; -static int __init g5_cpufreq_init(void) +static int __init g5_neo2_cpufreq_init(struct device_node *cpus) { struct device_node *cpunode; unsigned int psize, ssize; - struct smu_sdbp_header *shdr; unsigned long max_freq; - u32 *valp; + char *freq_method, *volt_method; + u32 *valp, pvr_hi; + int use_volts_vdnap = 0; + int use_volts_smu = 0; int rc = -ENODEV; - /* Look for CPU and SMU nodes */ - cpunode = of_find_node_by_type(NULL, "cpu"); - if (!cpunode) { - DBG("No CPU node !\n"); + /* Check supported platforms */ + if (machine_is_compatible("PowerMac8,1") || + machine_is_compatible("PowerMac8,2") || + machine_is_compatible("PowerMac9,1")) + use_volts_smu = 1; + else if (machine_is_compatible("PowerMac11,2")) + use_volts_vdnap = 1; + else + return -ENODEV; + + /* Get first CPU node */ + for (cpunode = NULL; + (cpunode = of_get_next_child(cpus, cpunode)) != NULL;) { + u32 *reg = + (u32 *)get_property(cpunode, "reg", NULL); + if (reg == NULL || (*reg) != 0) + continue; + if (!strcmp(cpunode->type, "cpu")) + break; + } + if (cpunode == NULL) { + printk(KERN_ERR "cpufreq: Can't find any CPU 0 node\n"); return -ENODEV; } @@ -246,8 +417,9 @@ DBG("No cpu-version property !\n"); goto bail_noprops; } - if (((*valp) >> 16) != 0x3c) { - DBG("Wrong CPU version: %08x\n", *valp); + pvr_hi = (*valp) >> 16; + if (pvr_hi != 0x3c && pvr_hi != 0x44) { + printk(KERN_ERR "cpufreq: Unsupported CPU version\n"); goto bail_noprops; } @@ -259,18 +431,50 @@ } g5_pmode_max = psize / sizeof(u32) - 1; - /* Look for the FVT table */ - shdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); - if (!shdr) - goto bail_noprops; - g5_fvt_table = (struct smu_sdbp_fvt *)&shdr[1]; - ssize = (shdr->len * sizeof(u32)) - sizeof(struct smu_sdbp_header); - g5_fvt_count = ssize / sizeof(struct smu_sdbp_fvt); - g5_fvt_cur = 0; + if (use_volts_smu) { + struct smu_sdbp_header *shdr; - /* Sanity checking */ - if (g5_fvt_count < 1 || g5_pmode_max < 1) - goto bail_noprops; + /* Look for the FVT table */ + shdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); + if (!shdr) + goto bail_noprops; + g5_fvt_table = (struct smu_sdbp_fvt *)&shdr[1]; + ssize = (shdr->len * sizeof(u32)) - + sizeof(struct smu_sdbp_header); + g5_fvt_count = ssize / sizeof(struct smu_sdbp_fvt); + g5_fvt_cur = 0; + + /* Sanity checking */ + if (g5_fvt_count < 1 || g5_pmode_max < 1) + goto bail_noprops; + + g5_switch_volt = g5_smu_switch_volt; + volt_method = "SMU"; + } else if (use_volts_vdnap) { + struct device_node *root; + + root = of_find_node_by_path("/"); + if (root == NULL) { + printk(KERN_ERR "cpufreq: Can't find root of " + "device tree\n"); + goto bail_noprops; + } + pfunc_set_vdnap0 = pmf_find_function(root, "set-vdnap0"); + pfunc_vdnap0_complete = + pmf_find_function(root, "slewing-done"); + if (pfunc_set_vdnap0 == NULL || + pfunc_vdnap0_complete == NULL) { + printk(KERN_ERR "cpufreq: Can't find required " + "platform function\n"); + goto bail_noprops; + } + + g5_switch_volt = g5_vdnap_switch_volt; + volt_method = "GPIO"; + } else { + g5_switch_volt = g5_dummy_switch_volt; + volt_method = "none"; + } /* * From what I see, clock-frequency is always the maximal frequency. @@ -286,19 +490,23 @@ g5_cpu_freqs[0].frequency = max_freq; g5_cpu_freqs[1].frequency = max_freq/2; - /* Check current frequency */ - g5_pmode_cur = g5_query_freq(); - if (g5_pmode_cur > 1) - /* We don't support anything but 1:1 and 1:2, fixup ... */ - g5_pmode_cur = 1; + /* Set callbacks */ + g5_switch_freq = g5_scom_switch_freq; + g5_query_freq = g5_scom_query_freq; + freq_method = "SCOM"; /* Force apply current frequency to make sure everything is in * sync (voltage is right for example). Firmware may leave us with * a strange setting ... */ - g5_switch_freq(g5_pmode_cur); + g5_switch_volt(CPUFREQ_HIGH); + msleep(10); + g5_pmode_cur = -1; + g5_switch_freq(g5_query_freq()); printk(KERN_INFO "Registering G5 CPU frequency driver\n"); + printk(KERN_INFO "Frequency method: %s, Voltage method: %s\n", + freq_method, volt_method); printk(KERN_INFO "Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n", g5_cpu_freqs[1].frequency/1000, g5_cpu_freqs[0].frequency/1000, @@ -317,6 +525,201 @@ return rc; } +static int __init g5_pm72_cpufreq_init(struct device_node *cpus) +{ + struct device_node *cpuid = NULL, *hwclock = NULL, *cpunode = NULL; + u8 *eeprom = NULL; + u32 *valp; + u64 max_freq, min_freq, ih, il; + int has_volt = 1, rc = 0; + + /* Get first CPU node */ + for (cpunode = NULL; + (cpunode = of_get_next_child(cpus, cpunode)) != NULL;) { + if (!strcmp(cpunode->type, "cpu")) + break; + } + if (cpunode == NULL) { + printk(KERN_ERR "cpufreq: Can't find any CPU node\n"); + return -ENODEV; + } + + /* Lookup the cpuid eeprom node */ + cpuid = of_find_node_by_path("/u3 at 0,f8000000/i2c at f8001000/cpuid at a0"); + if (cpuid != NULL) + eeprom = (u8 *)get_property(cpuid, "cpuid", NULL); + if (eeprom == NULL) { + printk(KERN_ERR "cpufreq: Can't find cpuid EEPROM !\n"); + rc = -ENODEV; + goto bail; + } + + /* Lookup the i2c hwclock */ + for (hwclock = NULL; + (hwclock = of_find_node_by_name(hwclock, "i2c-hwclock")) != NULL;){ + char *loc = get_property(hwclock, "hwctrl-location", NULL); + if (loc == NULL) + continue; + if (strcmp(loc, "CPU CLOCK")) + continue; + if (!get_property(hwclock, "platform-get-frequency", NULL)) + continue; + break; + } + if (hwclock == NULL) { + printk(KERN_ERR "cpufreq: Can't find i2c clock chip !\n"); + rc = -ENODEV; + goto bail; + } + + DBG("cpufreq: i2c clock chip found: %s\n", hwclock->full_name); + + /* Now get all the platform functions */ + pfunc_cpu_getfreq = + pmf_find_function(hwclock, "get-frequency"); + pfunc_cpu_setfreq_high = + pmf_find_function(hwclock, "set-frequency-high"); + pfunc_cpu_setfreq_low = + pmf_find_function(hwclock, "set-frequency-low"); + pfunc_slewing_done = + pmf_find_function(hwclock, "slewing-done"); + pfunc_cpu0_volt_high = + pmf_find_function(hwclock, "set-voltage-high-0"); + pfunc_cpu0_volt_low = + pmf_find_function(hwclock, "set-voltage-low-0"); + pfunc_cpu1_volt_high = + pmf_find_function(hwclock, "set-voltage-high-1"); + pfunc_cpu1_volt_low = + pmf_find_function(hwclock, "set-voltage-low-1"); + + /* Check we have minimum requirements */ + if (pfunc_cpu_getfreq == NULL || pfunc_cpu_setfreq_high == NULL || + pfunc_cpu_setfreq_low == NULL || pfunc_slewing_done == NULL) { + printk(KERN_ERR "cpufreq: Can't find platform functions !\n"); + rc = -ENODEV; + goto bail; + } + + /* Check that we have complete sets */ + if (pfunc_cpu0_volt_high == NULL || pfunc_cpu0_volt_low == NULL) { + pmf_put_function(pfunc_cpu0_volt_high); + pmf_put_function(pfunc_cpu0_volt_low); + pfunc_cpu0_volt_high = pfunc_cpu0_volt_low = NULL; + has_volt = 0; + } + if (!has_volt || + pfunc_cpu1_volt_high == NULL || pfunc_cpu1_volt_low == NULL) { + pmf_put_function(pfunc_cpu1_volt_high); + pmf_put_function(pfunc_cpu1_volt_low); + pfunc_cpu1_volt_high = pfunc_cpu1_volt_low = NULL; + } + + /* Note: The device tree also contains a "platform-set-values" + * function for which I haven't quite figured out the usage. It + * might have to be called on init and/or wakeup, I'm not too sure + * but things seem to work fine without it so far ... + */ + + /* Get max frequency from device-tree */ + valp = (u32 *)get_property(cpunode, "clock-frequency", NULL); + if (!valp) { + printk(KERN_ERR "cpufreq: Can't find CPU frequency !\n"); + rc = -ENODEV; + goto bail; + } + + max_freq = (*valp)/1000; + + /* Now calculate reduced frequency by using the cpuid input freq + * ratio. This requires 64 bits math unless we are willing to lose + * some precision + */ + + ih = *((u32 *)(eeprom + 0x10)); + il = *((u32 *)(eeprom + 0x20)); + min_freq = 0; + if (ih != 0 && il != 0) + min_freq = (max_freq * il) / ih; + + /* Sanity check */ + if (min_freq >= max_freq || min_freq < 1000) { + printk(KERN_ERR "cpufreq: Can't calculate low frequency !\n"); + rc = -ENODEV; + goto bail; + } + g5_cpu_freqs[0].frequency = max_freq; + g5_cpu_freqs[1].frequency = min_freq; + + /* Set callbacks */ + g5_switch_volt = g5_pfunc_switch_volt; + g5_switch_freq = g5_pfunc_switch_freq; + g5_query_freq = g5_pfunc_query_freq; + + /* Force apply current frequency to make sure everything is in + * sync (voltage is right for example). Firmware may leave us with + * a strange setting ... + */ + g5_switch_volt(CPUFREQ_HIGH); + msleep(10); + g5_pmode_cur = -1; + g5_switch_freq(g5_query_freq()); + + printk(KERN_INFO "Registering G5 CPU frequency driver\n"); + printk(KERN_INFO "Frequency method: i2c/pfunc, " + "Voltage method: %s\n", has_volt ? "i2c/pfunc" : "none"); + printk(KERN_INFO "Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n", + g5_cpu_freqs[1].frequency/1000, + g5_cpu_freqs[0].frequency/1000, + g5_cpu_freqs[g5_pmode_cur].frequency/1000); + + rc = cpufreq_register_driver(&g5_cpufreq_driver); + bail: + if (rc != 0) { + pmf_put_function(pfunc_cpu_getfreq); + pmf_put_function(pfunc_cpu_setfreq_high); + pmf_put_function(pfunc_cpu_setfreq_low); + pmf_put_function(pfunc_slewing_done); + pmf_put_function(pfunc_cpu0_volt_high); + pmf_put_function(pfunc_cpu0_volt_low); + pmf_put_function(pfunc_cpu1_volt_high); + pmf_put_function(pfunc_cpu1_volt_low); + } + of_node_put(hwclock); + of_node_put(cpuid); + of_node_put(cpunode); + + return rc; +} + +static int __init g5_rm31_cpufreq_init(struct device_node *cpus) +{ + /* NYI */ + return 0; +} + +static int __init g5_cpufreq_init(void) +{ + struct device_node *cpus; + int rc; + + cpus = of_find_node_by_path("/cpus"); + if (cpus == NULL) { + DBG("No /cpus node !\n"); + return -ENODEV; + } + + if (machine_is_compatible("PowerMac7,2") || + machine_is_compatible("PowerMac7,3")) + rc = g5_pm72_cpufreq_init(cpus); + else if (machine_is_compatible("RackMac3,1")) + rc = g5_rm31_cpufreq_init(cpus); + else + rc = g5_neo2_cpufreq_init(cpus); + + of_node_put(cpus); + return rc; +} + module_init(g5_cpufreq_init); From msdemlei at cl.uni-heidelberg.de Thu Jan 5 22:47:51 2006 From: msdemlei at cl.uni-heidelberg.de (Markus Demleitner) Date: Thu, 5 Jan 2006 12:47:51 +0100 Subject: Phantom pain with windfarm on diskless iMac G5 In-Reply-To: <1136418090.4840.21.camel@localhost.localdomain> References: <20060104142006.GA8782@victor.cl.uni-heidelberg.de> <1136418090.4840.21.camel@localhost.localdomain> Message-ID: <20060105114751.GB8959@victor.cl.uni-heidelberg.de> On Thu, Jan 05, 2006 at 10:41:29AM +1100, Benjamin Herrenschmidt wrote: > On Wed, 2006-01-04 at 15:20 +0100, Markus Demleitner wrote: > > Hi, > > > > I tried 2.6.15 on my diskless iMac G5 clients today, resulting in > > 747 emulation mode (vrooom...!). It turns out windfarm was > > querying the hard disk temperature sensor, which usually is mounted > > on the mounting bracket Apple uses. We made the mistake of removing > > these (from about 30 machines:-(), which in turn made > > some way to detect the absence of the sensor (and tell it from a > > simple failure). However, the OF device trees still list the sensor [...] > > Even if there were a way to detect the absence of the sensor, there's > > still the problem that windfarm_pm81.c insists on having a hd temp > > sensor to work, so a fix would probably require spoiling that > > wonderful [...] > > In short: Am I doomed to hack the kernels of my diskless clients to > > eternity (or retrofit the sensors)? Or is there a sane way to treat > > that kind of problem? > > Hrm... That isn't trivial as I don't see a clean way to detect that the > HD is not there from windfarm without doing gross hacks, unless we can > somewhat rely on the device-tree there... > > What we could do is: > > - Make pm81 start the control loops regardless of the presence of the > sensor, and have the control loop itself set the disk fan to an > arbitrary low value if the sensor is not there. If the sensor kicks in > "later" (because lm75 loads later), it will automatically start using > the full control loop. That is easy. If that works out, fine -- I could simply keep lm75 from loading and I'd be happy. However, I can see issues here of not detecting sensor or other failures. Worse, this isn't an issue of the disk fan (which my machines don't have anyway) alone, but also of the main fan (right now, we get an overtemp condition on top of that, with all consequences). > - In lm75 itself, in case of failure, add a little hack that tests if > the disk is present by looking in the device-tree, provided again that > there is a node for it that can be detected... If not, then return an > arbitrarily low temperature instead of a failure. I'd like that (it's basically what I'm doing now, except that I don't actually sense hd presence but just run the crippled kernels on machines without disks). Trouble is, as I said, that the dev tree doesn't reflect disk absence. Asking other parts of the kernel introduces more trouble (the ata interface may not be compiled in and in fact isn't on my client machines). > Either that or a module/kernel command line option... The later is > easier but less "neat" :) Since I have no idea how to solve the problem with the other "solutions", that's probably the best idea. What do you think of a parameter "debug_values" for windfarm_lm75_sensor.c that is either 0 (default) for returning actual sensor readings or some other value that's then returned (which might also help with debugging, and the naming of the parameter should make clear that you're not supposed to use it unless you know what you're doing)? While I'm about this: Did you resolve the issues with windfarm as modules? Thanks, Markus From schwab at suse.de Fri Jan 6 00:23:13 2006 From: schwab at suse.de (Andreas Schwab) Date: Thu, 05 Jan 2006 14:23:13 +0100 Subject: [PATCH] powerpc: Add PowerMac platform function interpreter In-Reply-To: <1136439584.4840.48.camel@localhost.localdomain> (Benjamin Herrenschmidt's message of "Thu, 05 Jan 2006 16:39:44 +1100") References: <1136439584.4840.48.camel@localhost.localdomain> Message-ID: Benjamin Herrenschmidt writes: > Anyway, here it is, comments welcome... To which tree is this relative? Neither 2.6.15 nor Linus' tree matches. Andreas. -- Andreas Schwab, SuSE Labs, schwab at suse.de SuSE Linux Products GmbH, Maxfeldstra?e 5, 90409 N?rnberg, Germany PGP key fingerprint = 58CA 54C7 6D53 942B 1756 01D3 44D5 214B 8276 4ED5 "And now for something completely different." From arnd at arndb.de Fri Jan 6 01:05:29 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Thu, 5 Jan 2006 14:05:29 +0000 Subject: [PATCH 13/13] spufs: set irq affinity for running threads In-Reply-To: <20060105044227.GD16729@localhost.localdomain> References: <20060104193120.050539000@localhost> <20060104194502.253418000@localhost> <20060105044227.GD16729@localhost.localdomain> Message-ID: <200601051405.30015.arnd@arndb.de> For far, all SPU triggered interrupts always end up on the first SMT thread, which is a bad solution. This patch implements setting the affinity to the CPU that was running last when entering execution on an SPU. This should result in a significant reduction in IPI calls and better cache locality for SPE thread specific data. Signed-off-by: Arnd Bergmann --- On Thursday 05 January 2006 04:42, Nathan Lynch wrote: > > + spu_irq_setaffinity(spu, smp_processor_id()); > > With CONFIG_DEBUG_PREEMPT this will give a warning about using > smp_processor_id in pre-emptible context if I'm reading the code > correctly. > > Maybe use raw_smp_processor_id, since setting the affinity to this cpu > isn't a hard requirement? Good point. Please use this version instead. Arnd <>< Index: linux-2.6.15-rc/include/asm-powerpc/spu.h =================================================================== --- linux-2.6.15-rc.orig/include/asm-powerpc/spu.h +++ linux-2.6.15-rc/include/asm-powerpc/spu.h @@ -147,6 +147,7 @@ struct spu *spu_alloc(void); void spu_free(struct spu *spu); int spu_irq_class_0_bottom(struct spu *spu); int spu_irq_class_1_bottom(struct spu *spu); +void spu_irq_setaffinity(struct spu *spu, int cpu); extern struct spufs_calls { asmlinkage long (*create_thread)(const char __user *name, Index: linux-2.6.15-rc/arch/powerpc/platforms/cell/interrupt.c =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/platforms/cell/interrupt.c +++ linux-2.6.15-rc/arch/powerpc/platforms/cell/interrupt.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,7 @@ struct iic_regs { struct iic { struct iic_regs __iomem *regs; + u8 target_id; }; static DEFINE_PER_CPU(struct iic, iic); @@ -172,12 +174,11 @@ int iic_get_irq(struct pt_regs *regs) return irq; } -static struct iic_regs __iomem *find_iic(int cpu) +static int setup_iic(int cpu, struct iic *iic) { struct device_node *np; int nodeid = cpu / 2; unsigned long regs; - struct iic_regs __iomem *iic_regs; for (np = of_find_node_by_type(NULL, "cpu"); np; @@ -188,20 +189,23 @@ static struct iic_regs __iomem *find_iic if (!np) { printk(KERN_WARNING "IIC: CPU %d not found\n", cpu); - iic_regs = NULL; - } else { - regs = *(long *)get_property(np, "iic", NULL); - - /* hack until we have decided on the devtree info */ - regs += 0x400; - if (cpu & 1) - regs += 0x20; - - printk(KERN_DEBUG "IIC for CPU %d at %lx\n", cpu, regs); - iic_regs = __ioremap(regs, sizeof(struct iic_regs), - _PAGE_NO_CACHE); + iic->regs = NULL; + iic->target_id = 0xff; + return -ENODEV; } - return iic_regs; + + regs = *(long *)get_property(np, "iic", NULL); + + /* hack until we have decided on the devtree info */ + regs += 0x400; + if (cpu & 1) + regs += 0x20; + + printk(KERN_DEBUG "IIC for CPU %d at %lx\n", cpu, regs); + iic->regs = __ioremap(regs, sizeof(struct iic_regs), + _PAGE_NO_CACHE); + iic->target_id = (nodeid << 4) + ((cpu & 1) ? 0xf : 0xe); + return 0; } #ifdef CONFIG_SMP @@ -227,6 +231,12 @@ void iic_cause_IPI(int cpu, int mesg) out_be64(&per_cpu(iic, cpu).regs->generate, (IIC_NUM_IPIS - 1 - mesg) << 4); } +u8 iic_get_target_id(int cpu) +{ + return per_cpu(iic, cpu).target_id; +} +EXPORT_SYMBOL_GPL(iic_get_target_id); + static irqreturn_t iic_ipi_action(int irq, void *dev_id, struct pt_regs *regs) { smp_message_recv(iic_irq_to_ipi(irq), regs); @@ -276,7 +286,7 @@ void iic_init_IRQ(void) irq_offset = 0; for_each_cpu(cpu) { iic = &per_cpu(iic, cpu); - iic->regs = find_iic(cpu); + setup_iic(cpu, iic); if (iic->regs) out_be64(&iic->regs->prio, 0xff); } Index: linux-2.6.15-rc/arch/powerpc/platforms/cell/interrupt.h =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/platforms/cell/interrupt.h +++ linux-2.6.15-rc/arch/powerpc/platforms/cell/interrupt.h @@ -54,6 +54,7 @@ extern void iic_setup_cpu(void); extern void iic_local_enable(void); extern void iic_local_disable(void); +extern u8 iic_get_target_id(int cpu); extern void spider_init_IRQ(void); extern int spider_get_irq(unsigned long int_pending); Index: linux-2.6.15-rc/arch/powerpc/platforms/cell/spu_base.c =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/platforms/cell/spu_base.c +++ linux-2.6.15-rc/arch/powerpc/platforms/cell/spu_base.c @@ -507,6 +507,14 @@ int spu_irq_class_1_bottom(struct spu *s return ret; } +void spu_irq_setaffinity(struct spu *spu, int cpu) +{ + u64 target = iic_get_target_id(cpu); + u64 route = target << 48 | target << 32 | target << 16; + spu_int_route_set(spu, route); +} +EXPORT_SYMBOL_GPL(spu_irq_setaffinity); + static void __iomem * __init map_spe_prop(struct device_node *n, const char *name) { Index: linux-2.6.15-rc/arch/powerpc/platforms/cell/spufs/sched.c =================================================================== --- linux-2.6.15-rc.orig/arch/powerpc/platforms/cell/spufs/sched.c +++ linux-2.6.15-rc/arch/powerpc/platforms/cell/spufs/sched.c @@ -357,6 +357,11 @@ int spu_activate(struct spu_context *ctx if (!spu) return (signal_pending(current)) ? -ERESTARTSYS : -EAGAIN; bind_context(spu, ctx); + /* + * We're likely to wait for interrupts on the same + * CPU that we are now on, so send them here. + */ + spu_irq_setaffinity(spu, raw_smp_processor_id()); put_active_spu(spu); return 0; } From benh at kernel.crashing.org Fri Jan 6 09:19:54 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Fri, 06 Jan 2006 09:19:54 +1100 Subject: [PATCH] powerpc: Add PowerMac platform function interpreter In-Reply-To: References: <1136439584.4840.48.camel@localhost.localdomain> Message-ID: <1136499595.4840.86.camel@localhost.localdomain> On Thu, 2006-01-05 at 14:23 +0100, Andreas Schwab wrote: > Benjamin Herrenschmidt writes: > > > Anyway, here it is, comments welcome... > > To which tree is this relative? Neither 2.6.15 nor Linus' tree matches. powerpc.git Ben. From torvalds at osdl.org Fri Jan 6 10:26:24 2006 From: torvalds at osdl.org (Linus Torvalds) Date: Thu, 5 Jan 2006 15:26:24 -0800 (PST) Subject: [patch 00/21] mutex subsystem, -V14 In-Reply-To: <43BDA672.4090704@austin.ibm.com> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> Message-ID: On Thu, 5 Jan 2006, Joel Schopp wrote: > > Here is a first pass at a powerpc file for the fast paths just as an FYI/RFC. > It is completely untested, but compiles. Shouldn't you make that "isync" dependent on SMP too? UP doesn't need it, since DMA will never matter, and interrupts are precise. Linus From jschopp at austin.ibm.com Fri Jan 6 10:36:42 2006 From: jschopp at austin.ibm.com (Joel Schopp) Date: Thu, 05 Jan 2006 17:36:42 -0600 Subject: [patch 00/21] mutex subsystem, -V14 In-Reply-To: References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> Message-ID: <43BDAD8A.60108@austin.ibm.com> >>Here is a first pass at a powerpc file for the fast paths just as an FYI/RFC. >>It is completely untested, but compiles. > > > Shouldn't you make that "isync" dependent on SMP too? UP doesn't need it, > since DMA will never matter, and interrupts are precise. > > Linus > I think the isync is necessary to keep heavily out of order processors from getting ahead of themselves even on UP. Scanning back through the powerpc spinlock code they seem to take the same view there as well. From benh at kernel.crashing.org Fri Jan 6 11:18:56 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Fri, 06 Jan 2006 11:18:56 +1100 Subject: Phantom pain with windfarm on diskless iMac G5 In-Reply-To: <20060105114751.GB8959@victor.cl.uni-heidelberg.de> References: <20060104142006.GA8782@victor.cl.uni-heidelberg.de> <1136418090.4840.21.camel@localhost.localdomain> <20060105114751.GB8959@victor.cl.uni-heidelberg.de> Message-ID: <1136506736.4840.108.camel@localhost.localdomain> On Thu, 2006-01-05 at 12:47 +0100, Markus Demleitner wrote: > If that works out, fine -- I could simply keep lm75 from loading and > I'd be happy. However, I can see issues here of not detecting sensor > or other failures. Worse, this isn't an issue of the disk fan (which > my machines don't have anyway) alone, but also of the main fan (right > now, we get an overtemp condition on top of that, with all > consequences). What overtemp condition ? You mean that you get _really_ overtemp when the disk is missing and the disk fan not blowing ? That's pretty bad... > Since I have no idea how to solve the problem with the other > "solutions", that's probably the best idea. What do you think of a > parameter "debug_values" for windfarm_lm75_sensor.c that is either 0 > (default) for returning actual sensor readings or some other value > that's then returned (which might also help with debugging, and the > naming of the parameter should make clear that you're not supposed to > use it unless you know what you're doing)? > > While I'm about this: Did you resolve the issues with windfarm as > modules? Not yet. Ben. From benh at kernel.crashing.org Fri Jan 6 11:21:26 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Fri, 06 Jan 2006 11:21:26 +1100 Subject: Phantom pain with windfarm on diskless iMac G5 In-Reply-To: <20060105114751.GB8959@victor.cl.uni-heidelberg.de> References: <20060104142006.GA8782@victor.cl.uni-heidelberg.de> <1136418090.4840.21.camel@localhost.localdomain> <20060105114751.GB8959@victor.cl.uni-heidelberg.de> Message-ID: <1136506887.4840.110.camel@localhost.localdomain> Can you send me a tarball of /proc/device-tree taken on one of those machines without the hard disk ? Thanks... Ben. From olof at lixom.net Fri Jan 6 11:29:19 2006 From: olof at lixom.net (Olof Johansson) Date: Thu, 5 Jan 2006 18:29:19 -0600 Subject: [patch 00/21] mutex subsystem, -V14 In-Reply-To: <43BDA672.4090704@austin.ibm.com> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> Message-ID: <20060106002919.GA29190@pb15.lixom.net> On Thu, Jan 05, 2006 at 05:06:26PM -0600, Joel Schopp wrote: > Here is a first pass at a powerpc file for the fast paths just as an > FYI/RFC. It is completely untested, but compiles. You really should test it, it saves reviewers time. It's not that hard to at least try booting it. Besides the isync comments earlier, there's a bunch of whitespace issues going on. Did you copy and paste the code from somewhere? If so, you should move the original copyright over too. All your macros use spaces instead of tabs up to the \, should be changed. All tmp variables should be ints, since the atomic_t counter is a 32-bit variable. If you use longs, and lwarx (loads 32-bit without sign extend), the comparison with < 0 will never be true. > Index: 2.6.15-mutex14/include/asm-powerpc/mutex.h > =================================================================== > --- 2.6.15-mutex14.orig/include/asm-powerpc/mutex.h 2006-01-04 14:46:31.%N -0600 > +++ 2.6.15-mutex14/include/asm-powerpc/mutex.h 2006-01-05 16:25:41.%N -0600 > @@ -1,9 +1,83 @@ > /* > - * Pull in the generic implementation for the mutex fastpath. > + * include/asm-powerpc/mutex.h No need to keep filenames in files. > * > - * TODO: implement optimized primitives instead, or leave the generic > - * implementation in place, or pick the atomic_xchg() based generic > - * implementation. (see asm-generic/mutex-xchg.h for details) > + * PowerPC optimized mutex locking primitives > + * > + * Please look into asm-generic/mutex-xchg.h for a formal definition. > + * Copyright (C) 2006 Joel Schopp , IBM > */ > +#ifndef _ASM_MUTEX_H > +#define _ASM_MUTEX_H > +#define __mutex_fastpath_lock(count, fail_fn)\ > +do{ \ > + long tmp; \ > + __asm__ __volatile__( \ > +"1: lwarx %0,0,%1\n" \ > +" addic %0,%0,-1\n" \ > +" stwcx. %0,0,%1\n" \ > +" bne- 1b\n" \ > +" isync \n" \ > + : "=&r" (tmp) \ > + : "r" (&(count)->counter) \ > + : "cr0", "memory"); \ > + if (unlikely(tmp < 0)) \ > + fail_fn(count); \ > +} while (0) trailing whitespace > + > +#define __mutex_fastpath_unlock(count, fail_fn)\ > +do{ \ > + long tmp; \ > + __asm__ __volatile__(SYNC_ON_SMP \ > +"1: lwarx %0,0,%1\n" \ > +" addic %0,%0,1\n" \ > +" stwcx. %0,0,%1\n" \ > +" bne- 1b\n" \ space vs tab > + : "=&r" (tmp) \ > + : "r" (&(count)->counter) \ > + : "cr0", "memory"); \ > + if (unlikely(tmp <= 0)) \ > + fail_fn(count); \ > +} while (0) > + > + > +static inline int trailing whitespace > +__mutex_fastpath_trylock(atomic_t* count, int (*fail_fn)(atomic_t*)) atomic_t *count > +{ > + long tmp; > + __asm__ __volatile__( > +"1: lwarx %0,0,%1\n" > +" cmpwi 0,%0,1\n" > +" bne- 2f\n" > +" stwcx. %0,0,%1\n" space vs tab on the above 4 lines Shouldn't you decrement the counter before the store? > +" bne- 1b\n" > +" isync\n" > +"2:" > + : "=&r" (tmp) > + : "r" (&(count)->counter) > + : "cr0", "memory"); > + > + return (int)tmp; > + > +} > + > +#define __mutex_slowpath_needs_to_unlock() 1 > > -#include > +static inline int trailing whitespace > +__mutex_fastpath_lock_retval(atomic_t* count, int (*fail_fn)(atomic_t *)) atomic_t *count > +{ > + long tmp; counter is a 32-bit variable, so should tmp be otherwise the < 0 comparison can never be true. > + __asm__ __volatile__( > +"1: lwarx %0,0,%1\n" > +" addic %0,%0,-1\n" > +" stwcx. %0,0,%1\n" > +" bne- 1b\n" > +" isync \n" > + : "=&r" (tmp) > + : "r" (&(count)->counter) > + : "cr0", "memory"); > + if (unlikely(tmp < 0)) > + return fail_fn(count); > + else > + return 0; > +} > +#endif From jschopp at austin.ibm.com Fri Jan 6 10:06:26 2006 From: jschopp at austin.ibm.com (Joel Schopp) Date: Thu, 05 Jan 2006 17:06:26 -0600 Subject: [patch 00/21] mutex subsystem, -V14 In-Reply-To: <20060105222106.GA26474@elte.hu> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> Message-ID: <43BDA672.4090704@austin.ibm.com> > ISYNC_ON_SMP flushes all speculative reads currently in the queue - and > is hence a smp_rmb_backwards() primitive [per my previous mail] - but > does not affect writes - correct? > > if that's the case, what prevents a store from within the critical > section going up to right after the EIEIO_ON_SMP, but before the > atomic-dec instructions? Does any of those instructions imply some > barrier perhaps? Are writes always ordered perhaps (like on x86 CPUs), > and hence the store before the bne is an effective write-barrier? It really makes more sense after reading PowerPC Book II, which you can find at this link, it was written by people who explain this for a living: http://www-128.ibm.com/developerworks/eserver/articles/archguide.html While isync technically doesn't order stores it does order instructions. The previous bne- must complete, that bne- is dependent on the previous stwcx being complete. So no stores are slipping up. To get a better explanation you will have to read the document yourself. Here is a first pass at a powerpc file for the fast paths just as an FYI/RFC. It is completely untested, but compiles. Signed-off-by: Joel Schopp -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: powerpcmutex.patch Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060105/e2d51f53/attachment.txt From mingo at elte.hu Fri Jan 6 10:42:22 2006 From: mingo at elte.hu (Ingo Molnar) Date: Fri, 6 Jan 2006 00:42:22 +0100 Subject: [patch 00/21] mutex subsystem, -V14 In-Reply-To: <43BDAD8A.60108@austin.ibm.com> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> <43BDAD8A.60108@austin.ibm.com> Message-ID: <20060105234222.GA11474@elte.hu> * Joel Schopp wrote: > > Shouldn't you make that "isync" dependent on SMP too? UP doesn't > > need it, since DMA will never matter, and interrupts are precise. > > I think the isync is necessary to keep heavily out of order processors > from getting ahead of themselves even on UP. Scanning back through > the powerpc spinlock code they seem to take the same view there as > well. the asm/spinlock.h ops are only built on SMP kernels. mutex.h is for both UP and SMP. On UP you should need no synchronization, because the only way another context could interfere with your critical section is by getting interrupted, and interrupts are fully synchronizing, right? On UP the only synchronization needed is when a device reads/writes memory in parallel to the CPU. Ingo From miltonm at bga.com Fri Jan 6 19:00:16 2006 From: miltonm at bga.com (Milton Miller) Date: Fri, 6 Jan 2006 02:00:16 -0600 Subject: [PATCH, version 7] cell: enable pause(0) in cpu_idle In-Reply-To: <200601041955.53577.arnd@arndb.de> References: <200512171228.21578.arnd@arndb.de> <200512201314.12932.arnd@arndb.de> <200601041955.53577.arnd@arndb.de> Message-ID: <8f3b7d3eddbe59eecea11d8eecebda68@bga.com> > The only comment I got for version 6 was about formatting of > C style comments and I fixed those up, so let's hope this is > the final version of the patch. > You know, that just sounds like a challange :-) > + > +int cbe_system_reset_exception(struct pt_ static (used in cbe_pervasive_init at the bottom of the same file). Ok I should have seen that last time. milton From anton at samba.org Sat Jan 7 00:49:49 2006 From: anton at samba.org (Anton Blanchard) Date: Sat, 7 Jan 2006 00:49:49 +1100 Subject: [PATCH] ppc64: fix time syscall Message-ID: <20060106134948.GI26499@krispykreme> ppc64 has its own version of sys_time. It looks pretty scary, touching a whole bunch of variables without any locking or memory ordering. In fact, a recent bugreport has shown it can actually go backwards. Time to remove it and just use the generic sys_time, which is implemented on top of do_gettimeofday. Signed-off-by: Anton Blanchard --- Index: build/arch/powerpc/kernel/syscalls.c =================================================================== --- build.orig/arch/powerpc/kernel/syscalls.c 2005-12-20 19:08:01.000000000 +1100 +++ build/arch/powerpc/kernel/syscalls.c 2006-01-07 00:37:26.000000000 +1100 @@ -43,9 +43,6 @@ #include #include -extern unsigned long wall_jiffies; - - /* * sys_ipc() is the de-multiplexer for the SysV IPC calls.. * @@ -311,31 +308,6 @@ return error? -EFAULT: 0; } -#ifdef CONFIG_PPC64 -time_t sys64_time(time_t __user * tloc) -{ - time_t secs; - time_t usecs; - - long tb_delta = tb_ticks_since(tb_last_stamp); - tb_delta += (jiffies - wall_jiffies) * tb_ticks_per_jiffy; - - secs = xtime.tv_sec; - usecs = (xtime.tv_nsec/1000) + tb_delta / tb_ticks_per_usec; - while (usecs >= USEC_PER_SEC) { - ++secs; - usecs -= USEC_PER_SEC; - } - - if (tloc) { - if (put_user(secs,tloc)) - secs = -EFAULT; - } - - return secs; -} -#endif - long ppc_fadvise64_64(int fd, int advice, u32 offset_high, u32 offset_low, u32 len_high, u32 len_low) { Index: build/arch/powerpc/kernel/systbl.S =================================================================== --- build.orig/arch/powerpc/kernel/systbl.S 2005-11-05 20:51:08.000000000 +1100 +++ build/arch/powerpc/kernel/systbl.S 2006-01-07 00:37:26.000000000 +1100 @@ -54,7 +54,7 @@ SYSCALL(unlink) COMPAT_SYS(execve) SYSCALL(chdir) -SYSX(sys64_time,compat_sys_time,sys_time) +COMPAT_SYS(time) SYSCALL(mknod) SYSCALL(chmod) SYSCALL(lchown) From clameter at engr.sgi.com Sat Jan 7 07:24:25 2006 From: clameter at engr.sgi.com (Christoph Lameter) Date: Fri, 6 Jan 2006 12:24:25 -0800 (PST) Subject: [ANNOUNCE] numactl 0.9 released In-Reply-To: <200601032257.53039.ak@suse.de> References: <200601032257.53039.ak@suse.de> Message-ID: Here is a patch in order to make numactl support page migration. Comments welcome. Index: numactl-0.9/mbind.2 =================================================================== --- numactl-0.9.orig/mbind.2 2004-06-06 07:12:13.000000000 -0700 +++ numactl-0.9/mbind.2 2006-01-06 10:17:49.000000000 -0800 @@ -67,6 +67,30 @@ parameter will be returned when the existing pages in the mapping don't follow the policy. +When +.B MPOL_MF_MOVE +is passed in the +.B flags +then attempts will be made to move all the pages in the mapping +so that they follow the policy. Pages that are shared with other +processes are not moved. If +.B MPOL_MF_STRICT +is also specified then +.I EIO +will be returned if some pages could not be moved. + +When +.B MPOL_MF_MOVE_ALL +is passed in the +.B flags +then all pages in the mapping will be moved regardless of whether +other processes use the pages. The process specifying this flag must +have administrative priviledges. If +.B MPOL_MF_STRICT +is also specified then +.I EIO +will be returned if some pages could not be moved. + The .I MPOL_DEFAULT policy is the default and means to use the underlying process policy @@ -133,6 +157,9 @@ header. is ignored on huge page mappings right now. For preferred and interleave mappings it will only accept the first choice node. +.I MPOL_MF_MOVE_* +is only available on Linux 2.6.16 and later. + For .I MPOL_INTERLEAVE mode the interleaving is changed at fault time. The final layout of Index: numactl-0.9/numaif.h =================================================================== --- numactl-0.9.orig/numaif.h 2005-02-11 02:26:47.000000000 -0800 +++ numactl-0.9/numaif.h 2006-01-06 10:59:12.000000000 -0800 @@ -15,6 +15,8 @@ extern long mbind(void *start, unsigned const unsigned long *nmask, unsigned long maxnode, unsigned flags); extern long set_mempolicy(int mode, const unsigned long *nmask, unsigned long maxnode); +extern long migratepages(int pid, unsigned long maxnode, unsigned long *fromnode, + unsigned long *tonode); /* Policies */ #define MPOL_DEFAULT 0 @@ -30,6 +32,8 @@ extern long set_mempolicy(int mode, cons /* Flags for mbind */ #define MPOL_MF_STRICT (1<<0) /* Verify existing pages in the mapping */ +#define MPOL_MF_MOVE (1<<1) /* Move pages owned by this process to conform to mapping */ +#define MPOL_MF_MOVE_ALL (1<<2) /* Move every page to conform to mapping */ #ifdef __cplusplus } Index: numactl-0.9/Makefile =================================================================== --- numactl-0.9.orig/Makefile 2006-01-03 12:36:13.000000000 -0800 +++ numactl-0.9/Makefile 2006-01-06 12:12:14.000000000 -0800 @@ -25,12 +25,14 @@ prefix := /usr libdir := ${prefix}$(shell if [ -d /usr/lib64 ] ; then echo "/lib64" ; else echo "/lib" ; fi) docdir := ${prefix}/share/doc -all: numactl libnuma.so numademo numamon memhog test/tshared stream \ +all: numactl migratepages libnuma.so numademo numamon memhog test/tshared stream \ test/mynode test/pagesize test/ftok test/prefered test/randmap \ test/nodemap test/distance numactl: numactl.o util.o shm.o bitops.o libnuma.so +migratepages: migratepages.c util.o bitops.o libnuma.so + util.o: util.c memhog: util.o memhog.o libnuma.so @@ -94,10 +96,11 @@ set_membind set_preferred set_strict set tonodemask_memory distance MANPAGES := numa.3 numactl.8 mbind.2 set_mempolicy.2 get_mempolicy.2 \ - numastat.8 + numastat.8 migratepages.8 -install: numactl numademo.c numamon memhog libnuma.so.1 numa.h numaif.h numastat ${MANPAGES} +install: numactl migratepages numademo.c numamon memhog libnuma.so.1 numa.h numaif.h numastat ${MANPAGES} cp numactl ${prefix}/bin + cp migratepages ${prefix}/bin cp numademo ${prefix}/bin cp memhog ${prefix}/bin cp set_mempolicy.2 ${prefix}/share/man/man2 Index: numactl-0.9/migratepages.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ numactl-0.9/migratepages.c 2006-01-06 12:20:11.000000000 -0800 @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005 Christoph Lameter, Silicon Graphics, Incorporated. + * based on Andi Kleen's numactl.c. + * + * Manual process migration + * + * migratepages 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; version 2. + * + * migratepages 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 find a copy of v2 of the GNU General Public License somewhere + * on your Linux system; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include "numaif.h" +#include "numa.h" +#include "numaint.h" +#include "util.h" + +struct option opts[] = { + {"help", 0, 0, 'h' }, + { 0 } +}; + +void usage(void) +{ + fprintf(stderr, + "usage: migratepages pid from-nodes to-nodes\n" + "\n" + "nodes is a comma delimited list of node numbers or A-B ranges or none/all.\n" +); + exit(1); +} + +void checknuma(void) +{ + static int numa = -1; + if (numa < 0) { + if (numa_available() < 0) + complain("This system does not support NUMA functionality"); + } + numa = 0; +} + +int main(int argc, char *argv[]) +{ + int c; + char *end; + int rc; + int pid; + nodemask_t fromnodes; + nodemask_t tonodes; + + while ((c = getopt_long(argc,argv,"h", opts, NULL)) != -1) { + switch (c) { + default: + usage(); + } + } + + argv += optind; + argc -= optind; + + if (argc != 4) + usage(); + + checknuma(); + + pid = strtoul(argv[1], &end, 0); + if (*end) + usage(); + + fromnodes = nodemask(argv[2]); + tonodes = nodemask(argv[3]); + + rc = numa_migrate_pages(pid, &fromnodes, &tonodes); + + if (rc) { + perror("migrate_pages"); + return 1; + } + return 0; +} Index: numactl-0.9/syscall.c =================================================================== --- numactl-0.9.orig/syscall.c 2006-01-03 10:49:17.000000000 -0800 +++ numactl-0.9/syscall.c 2006-01-06 11:55:36.000000000 -0800 @@ -35,10 +35,12 @@ #define __NR_mbind 237 #define __NR_set_mempolicy 238 #define __NR_get_mempolicy 239 +#define __NR_migrate_pages 256 #elif defined(__ia64__) #define __NR_sched_setaffinity 1231 #define __NR_sched_getaffinity 1232 +#define __NR_migrate_pages 1280 /* Official allocation */ @@ -51,12 +53,14 @@ #define __NR_mbind 274 #define __NR_get_mempolicy 275 #define __NR_set_mempolicy 276 +#define __NR_migrate_pages 294 #elif defined(__powerpc__) #define __NR_mbind 259 #define __NR_get_mempolicy 260 #define __NR_set_mempolicy 261 +#define __NR_migrate_pages 280 #elif !defined(DEPS_RUN) #error "Add syscalls for your architecture or update kernel headers" @@ -141,6 +145,12 @@ long WEAK set_mempolicy(int mode, const return syscall(__NR_set_mempolicy,mode,nmask,maxnode); } +long WEAK migrate_pages(int pid, unsigned long maxnode, + const unsigned long *frommask, const unsigned long *tomask) +{ + return syscall(__NR_migrate_pages, pid, maxnode, frommask, tomask); +} + /* SLES8 glibc doesn't define those */ int numa_sched_setaffinity(pid_t pid, unsigned len, const unsigned long *mask) @@ -159,3 +169,5 @@ make_internal_alias(numa_sched_setaffini make_internal_alias(get_mempolicy); make_internal_alias(set_mempolicy); make_internal_alias(mbind); +make_internal_alias(migrate_pages); + Index: numactl-0.9/numa.h =================================================================== --- numactl-0.9.orig/numa.h 2005-12-25 14:20:34.000000000 -0800 +++ numactl-0.9/numa.h 2006-01-06 11:40:25.000000000 -0800 @@ -176,6 +176,8 @@ extern int numa_exit_on_error; once. */ void numa_warn(int num, char *fmt, ...); +int numa_migrate_pages(int pid, const nodemask_t *from, const nodemask_t *to); + #ifdef __cplusplus } #endif Index: numactl-0.9/libnuma.c =================================================================== --- numactl-0.9.orig/libnuma.c 2005-12-19 04:11:51.000000000 -0800 +++ numactl-0.9/libnuma.c 2006-01-06 12:00:03.000000000 -0800 @@ -600,6 +600,19 @@ nodemask_t numa_get_run_node_mask(void) return mask; } +int numa_migrate_pages(int pid, const nodemask_t *fromnodes, const nodemask_t *tonodes) +{ + int err; + + err = migrate_pages(pid, NUMA_NUM_NODES + 1, &fromnodes->n[0], &tonodes->n[0]); + + if (err < 0) { + errno = -err; + return -1; + } + return err; +} + int numa_run_on_node(int node) { int ncpus = number_of_cpus(); Index: numactl-0.9/numaint.h =================================================================== --- numactl-0.9.orig/numaint.h 2005-04-28 04:40:38.000000000 -0700 +++ numactl-0.9/numaint.h 2006-01-06 11:56:36.000000000 -0800 @@ -11,7 +11,9 @@ extern long mbind_int(void *start, unsig const unsigned long *nmask, unsigned long maxnode, unsigned flags); extern long set_mempolicy_int(int mode, const unsigned long *nmask, unsigned long maxnode); - +extern long migrate_pages(int pid, unsigned long maxnode, const unsigned long *frommask, + const unsigned long *tomask); + #define SHM_HUGETLB 04000 /* segment will use huge TLB pages */ #define CPU_BYTES(x) (round_up(x, BITS_PER_LONG)/8) Index: numactl-0.9/migratepages.8 =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ numactl-0.9/migratepages.8 2006-01-06 12:18:03.000000000 -0800 @@ -0,0 +1,63 @@ +.\" t +.\" Copyright 2005 Christoph Lameter, Silicon Graphics, Inc. +.\" +.\" based on Andi Kleen's numactl manpage +.\" +.TH MIGRATEPAGES 8 "Jan 2005" "SGI" "Linux Administrator's Manual" +.SH NAME +migratepages \- Migrate the physical location of pages of a process +.SH SYNOPSIS +.B migratepages +pid from-nodes to-nodes +.SH DESCRIPTION +.B migratepages +moves the physical localtion of a processes pages without any changes of the +virtual address space of the process. This is usually done to optimize +the performance of a process by moving the pages near to the processor +executing a process. +.TP +Valid node specifiers +.TS +tab(:); +l l. +all:All nodes +number:Node number +number1{,number2}:Node number1 and Node number2 +number1-number2:Nodes from number1 to number2 +! nodes:Invert selection of the following specification. +.TE +.SH NOTES +Requires an NUMA policy aware kernel. + +migratepages will only move pages that are not shared with other +processes if called by a user without administrative priviledges (but +with the right to modify the process). + +migratepages will move all pages if invoked from root (or a user with +administrative priviledges). + +.SH FILES +.I /proc//numastat +for information about the NUMA memory use of a process. +.SH COPYRIGHT +Copyright 2005 Christoph Lameter, Silicon Graphics, Inc. +migratepages is under the GNU General Public License, v.2 + +.SH SEE ALSO +.I numactl(8) +, +.I set_mempolicy(2) +, +.I get_mempolicy(2) +, +.I mbind(2) +, +.I sched_setaffinity(2) +, +.I sched_getaffinity(2) +, +.I proc(5) +, +.I ftok(3) +, +.I shmat(2) Index: numactl-0.9/numactl.8 =================================================================== --- numactl-0.9.orig/numactl.8 2005-12-16 04:13:19.000000000 -0800 +++ numactl-0.9/numactl.8 2006-01-06 12:11:31.000000000 -0800 @@ -271,3 +271,6 @@ numactl and the demo programs are under .I ftok(3) , .I shmat(2) +, +.I migratepages(8) + From ak at suse.de Sat Jan 7 08:10:30 2006 From: ak at suse.de (Andi Kleen) Date: Fri, 6 Jan 2006 22:10:30 +0100 Subject: [ANNOUNCE] numactl 0.9 released In-Reply-To: References: <200601032257.53039.ak@suse.de> Message-ID: <200601062210.31154.ak@suse.de> On Friday 06 January 2006 21:24, Christoph Lameter wrote: > Here is a patch in order to make numactl support page migration. Thanks, Did migrate_pages make it into Linus' tree yet? I don't see it there yet. I think the name of the command is a bit unintuitive - how about calling it changenodemem or movenodemem ? (better suggestions welcome) At least some words in the manpage what happens when you specify multiple nodes in from/to might be a good idea. > + pid = strtoul(argv[1], &end, 0); > + if (*end) || end == argv[1] > +moves the physical localtion of a processes pages without any changes of the typo. > +.SH COPYRIGHT > +Copyright 2005 Christoph Lameter, Silicon Graphics, Inc. > +migratepages is under the GNU General Public License, v.2 I can only warn you of putting your name visibly into the manpages - it will give you a constant stream of mails from confused users for many years ;-) > +, > +.I shmat(2) Refer to taskset too? (or alternatively add a option to set the CPU affinity using cpus or nodes too - taskset has the disadvantage that you cannot specify nodes so a user would need to know the cpu<->node mapping) -Andi From benh at kernel.crashing.org Sat Jan 7 11:24:30 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sat, 07 Jan 2006 11:24:30 +1100 Subject: [PATCH] 0/5 powerpc: Platform & i2c updates & cpufreq Message-ID: <1136593470.4840.154.camel@localhost.localdomain> Hi ! This serie of patch replaces the 2 I posted recently for platform functions & g5 cpufreq. It's not a 100% finished job yet but it's getting closer. Ben. From benh at kernel.crashing.org Sat Jan 7 11:30:44 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sat, 07 Jan 2006 11:30:44 +1100 Subject: [PATCH] 1/5 powerpc: Rework PowerMac i2c part 1 Message-ID: <1136593845.4840.161.camel@localhost.localdomain> This is the first part of a rework of the PowerMac i2c code. It completely reworks the "low_i2c" layer. It is now more flexible, supports KeyWest, SMU and PMU i2c busses, and provides functions to match device nodes to i2c busses and adapters. This patch also extends & fix some bugs in the SMU driver related to i2c support and removes the clock spreading hacks from the pmac feature code rather than adapting them to the new API since they'll be replaced by the platform function code completely in patch 3/5 Signed-off-by: Benjamin Herrenschmidt Index: linux-work/arch/powerpc/platforms/powermac/feature.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/feature.c 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/feature.c 2006-01-07 11:25:16.000000000 +1100 @@ -1677,124 +1677,6 @@ intrepid_shutdown(struct macio_chip *mac } -void pmac_tweak_clock_spreading(int enable) -{ - struct macio_chip *macio = &macio_chips[0]; - - /* Hack for doing clock spreading on some machines PowerBooks and - * iBooks. This implements the "platform-do-clockspreading" OF - * property as decoded manually on various models. For safety, we also - * check the product ID in the device-tree in cases we'll whack the i2c - * chip to make reasonably sure we won't set wrong values in there - * - * Of course, ultimately, we have to implement a real parser for - * the platform-do-* stuff... - */ - - if (macio->type == macio_intrepid) { - struct device_node *clock = - of_find_node_by_path("/uni-n at f8000000/hw-clock"); - if (clock && get_property(clock, "platform-do-clockspreading", - NULL)) { - printk(KERN_INFO "%sabling clock spreading on Intrepid" - " ASIC\n", enable ? "En" : "Dis"); - if (enable) - UN_OUT(UNI_N_CLOCK_SPREADING, 2); - else - UN_OUT(UNI_N_CLOCK_SPREADING, 0); - mdelay(40); - } - of_node_put(clock); - } - - while (machine_is_compatible("PowerBook5,2") || - machine_is_compatible("PowerBook5,3") || - machine_is_compatible("PowerBook6,2") || - machine_is_compatible("PowerBook6,3")) { - struct device_node *ui2c = of_find_node_by_type(NULL, "i2c"); - struct device_node *dt = of_find_node_by_name(NULL, "device-tree"); - u8 buffer[9]; - u32 *productID; - int i, rc, changed = 0; - - if (dt == NULL) - break; - productID = (u32 *)get_property(dt, "pid#", NULL); - if (productID == NULL) - break; - while(ui2c) { - struct device_node *p = of_get_parent(ui2c); - if (p && !strcmp(p->name, "uni-n")) - break; - ui2c = of_find_node_by_type(ui2c, "i2c"); - } - if (ui2c == NULL) - break; - DBG("Trying to bump clock speed for PID: %08x...\n", *productID); - rc = pmac_low_i2c_open(ui2c, 1); - if (rc != 0) - break; - pmac_low_i2c_setmode(ui2c, pmac_low_i2c_mode_combined); - rc = pmac_low_i2c_xfer(ui2c, 0xd2 | pmac_low_i2c_read, 0x80, buffer, 9); - DBG("read result: %d,", rc); - if (rc != 0) { - pmac_low_i2c_close(ui2c); - break; - } - for (i=0; i<9; i++) - DBG(" %02x", buffer[i]); - DBG("\n"); - - switch(*productID) { - case 0x1182: /* AlBook 12" rev 2 */ - case 0x1183: /* iBook G4 12" */ - buffer[0] = (buffer[0] & 0x8f) | 0x70; - buffer[2] = (buffer[2] & 0x7f) | 0x00; - buffer[5] = (buffer[5] & 0x80) | 0x31; - buffer[6] = (buffer[6] & 0x40) | 0xb0; - buffer[7] = (buffer[7] & 0x00) | (enable ? 0xc0 : 0xba); - buffer[8] = (buffer[8] & 0x00) | 0x30; - changed = 1; - break; - case 0x3142: /* AlBook 15" (ATI M10) */ - case 0x3143: /* AlBook 17" (ATI M10) */ - buffer[0] = (buffer[0] & 0xaf) | 0x50; - buffer[2] = (buffer[2] & 0x7f) | 0x00; - buffer[5] = (buffer[5] & 0x80) | 0x31; - buffer[6] = (buffer[6] & 0x40) | 0xb0; - buffer[7] = (buffer[7] & 0x00) | (enable ? 0xd0 : 0xc0); - buffer[8] = (buffer[8] & 0x00) | 0x30; - changed = 1; - break; - default: - DBG("i2c-hwclock: Machine model not handled\n"); - break; - } - if (!changed) { - pmac_low_i2c_close(ui2c); - break; - } - printk(KERN_INFO "%sabling clock spreading on i2c clock chip\n", - enable ? "En" : "Dis"); - - pmac_low_i2c_setmode(ui2c, pmac_low_i2c_mode_stdsub); - rc = pmac_low_i2c_xfer(ui2c, 0xd2 | pmac_low_i2c_write, 0x80, buffer, 9); - DBG("write result: %d,", rc); - pmac_low_i2c_setmode(ui2c, pmac_low_i2c_mode_combined); - rc = pmac_low_i2c_xfer(ui2c, 0xd2 | pmac_low_i2c_read, 0x80, buffer, 9); - DBG("read result: %d,", rc); - if (rc != 0) { - pmac_low_i2c_close(ui2c); - break; - } - for (i=0; i<9; i++) - DBG(" %02x", buffer[i]); - pmac_low_i2c_close(ui2c); - break; - } -} - - static int core99_sleep(void) { @@ -2980,12 +2862,6 @@ set_initial_features(void) MACIO_BIC(HEATHROW_FCR, HRW_SOUND_POWER_N); } - /* Some machine models need the clock chip to be properly setup for - * clock spreading now. This should be a platform function but we - * don't do these at the moment - */ - pmac_tweak_clock_spreading(1); - #endif /* CONFIG_POWER4 */ /* On all machines, switch modem & serial ports off */ @@ -3013,9 +2889,6 @@ pmac_feature_init(void) return; } - /* Setup low-level i2c stuffs */ - pmac_init_low_i2c(); - /* Probe machine type */ if (probe_motherboard()) printk(KERN_WARNING "Unknown PowerMac !\n"); Index: linux-work/arch/powerpc/platforms/powermac/low_i2c.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/low_i2c.c 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/low_i2c.c 2006-01-07 11:25:18.000000000 +1100 @@ -1,22 +1,34 @@ /* - * arch/ppc/platforms/pmac_low_i2c.c + * arch/powerpc/platforms/powermac/low_i2c.c * - * Copyright (C) 2003 Ben. Herrenschmidt (benh at kernel.crashing.org) + * Copyright (C) 2003-2005 Ben. Herrenschmidt (benh at kernel.crashing.org) * * 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 file contains some low-level i2c access routines that - * need to be used by various bits of the PowerMac platform code - * at times where the real asynchronous & interrupt driven driver - * cannot be used. The API borrows some semantics from the darwin - * driver in order to ease the implementation of the platform - * properties parser + * The linux i2c layer isn't completely suitable for our needs for various + * reasons ranging from too late initialisation to semantics not perfectly + * matching some requirements of the apple platform functions etc... + * + * This file thus provides a simple low level unified i2c interface for + * powermac that covers the various types of i2c busses used in Apple machines. + * For now, keywest, PMU and SMU, though we could add Cuda, or other bit + * banging busses found on older chipstes in earlier machines if we ever need + * one of them. + * + * The drivers in this file are synchronous/blocking. In addition, the + * keywest one is fairly slow due to the use of msleep instead of interrupts + * as the interrupt is currently used by i2c-keywest. In the long run, we + * might want to get rid of those high-level interfaces to linux i2c layer + * either completely (converting all drivers) or replacing them all with a + * single stub driver on top of this one. Once done, the interrupt will be + * available for our use. */ #undef DEBUG +#undef DEBUG_LOW #include #include @@ -25,15 +37,16 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include -#define MAX_LOW_I2C_HOST 4 - #ifdef DEBUG #define DBG(x...) do {\ printk(KERN_DEBUG "low_i2c:" x); \ @@ -42,49 +55,54 @@ #define DBG(x...) #endif -struct low_i2c_host; - -typedef int (*low_i2c_func_t)(struct low_i2c_host *host, u8 addr, u8 sub, u8 *data, int len); - -struct low_i2c_host -{ - struct device_node *np; /* OF device node */ - struct semaphore mutex; /* Access mutex for use by i2c-keywest */ - low_i2c_func_t func; /* Access function */ - unsigned int is_open : 1; /* Poor man's access control */ - int mode; /* Current mode */ - int channel; /* Current channel */ - int num_channels; /* Number of channels */ - void __iomem *base; /* For keywest-i2c, base address */ - int bsteps; /* And register stepping */ - int speed; /* And speed */ -}; - -static struct low_i2c_host low_i2c_hosts[MAX_LOW_I2C_HOST]; +#ifdef DEBUG_LOW +#define DBG_LOW(x...) do {\ + printk(KERN_DEBUG "low_i2c:" x); \ + } while(0) +#else +#define DBG_LOW(x...) +#endif -/* No locking is necessary on allocation, we are running way before - * anything can race with us +/* + * A bus structure. Each bus in the system has such a structure associated. */ -static struct low_i2c_host *find_low_i2c_host(struct device_node *np) +struct pmac_i2c_bus { - int i; + struct list_head link; + struct device_node *controller; + struct device_node *busnode; + int type; + int flags; + struct i2c_adapter *adapter; + void *hostdata; + int channel; /* some hosts have multiple */ + int mode; /* current mode */ + struct semaphore sem; + int opened; + int polled; /* open mode */ + + /* ops */ + int (*open)(struct pmac_i2c_bus *bus); + void (*close)(struct pmac_i2c_bus *bus); + int (*xfer)(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len); +}; - for (i = 0; i < MAX_LOW_I2C_HOST; i++) - if (low_i2c_hosts[i].np == np) - return &low_i2c_hosts[i]; - return NULL; -} +static LIST_HEAD(pmac_i2c_busses); /* - * - * i2c-keywest implementation (UniNorth, U2, U3, Keylargo's) - * + * Keywest implementation */ -/* - * Keywest i2c definitions borrowed from drivers/i2c/i2c-keywest.h, - * should be moved somewhere in include/asm-ppc/ - */ +struct pmac_i2c_host_kw +{ + struct semaphore mutex; /* Access mutex for use by + * i2c-keywest */ + void __iomem *base; /* register base address */ + int bsteps; /* register stepping */ + int speed; /* speed */ +}; + /* Register indices */ typedef enum { reg_mode = 0, @@ -153,52 +171,56 @@ static const char *__kw_state_names[] = "state_dead" }; -static inline u8 __kw_read_reg(struct low_i2c_host *host, reg_t reg) +static inline u8 __kw_read_reg(struct pmac_i2c_bus *bus, reg_t reg) { + struct pmac_i2c_host_kw *host = bus->hostdata; return readb(host->base + (((unsigned int)reg) << host->bsteps)); } -static inline void __kw_write_reg(struct low_i2c_host *host, reg_t reg, u8 val) +static inline void __kw_write_reg(struct pmac_i2c_bus *bus, reg_t reg, u8 val) { + struct pmac_i2c_host_kw *host = bus->hostdata; writeb(val, host->base + (((unsigned)reg) << host->bsteps)); - (void)__kw_read_reg(host, reg_subaddr); + (void)__kw_read_reg(bus, reg_subaddr); } -#define kw_write_reg(reg, val) __kw_write_reg(host, reg, val) -#define kw_read_reg(reg) __kw_read_reg(host, reg) +#define kw_write_reg(reg, val) __kw_write_reg(bus, reg, val) +#define kw_read_reg(reg) __kw_read_reg(bus, reg) - -/* Don't schedule, the g5 fan controller is too - * timing sensitive - */ -static u8 kw_wait_interrupt(struct low_i2c_host* host) +static u8 kw_i2c_wait_interrupt(struct pmac_i2c_bus* bus) { int i, j; u8 isr; - for (i = 0; i < 100000; i++) { + for (i = 0; i < 1000; i++) { isr = kw_read_reg(reg_isr) & KW_I2C_IRQ_MASK; if (isr != 0) return isr; /* This code is used with the timebase frozen, we cannot rely - * on udelay ! For now, just use a bogus loop + * on udelay nor schedule when in polled mode ! + * For now, just use a bogus loop.... */ - for (j = 1; j < 10000; j++) - mb(); + if (bus->polled) { + for (j = 1; j < 1000000; j++) + mb(); + } else + msleep(1); } return isr; } -static int kw_handle_interrupt(struct low_i2c_host *host, int state, int rw, int *rc, u8 **data, int *len, u8 isr) +static int kw_i2c_handle_interrupt(struct pmac_i2c_bus *bus, int state, int rw, + int *rc, u8 **data, int *len, u8 isr) { u8 ack; - DBG("kw_handle_interrupt(%s, isr: %x)\n", __kw_state_names[state], isr); + DBG_LOW("kw_handle_interrupt(%s, isr: %x)\n", + __kw_state_names[state], isr); if (isr == 0) { if (state != state_stop) { - DBG("KW: Timeout !\n"); + DBG_LOW("KW: Timeout !\n"); *rc = -EIO; goto stop; } @@ -220,15 +242,16 @@ static int kw_handle_interrupt(struct lo *rc = -EIO; goto stop; } - if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { + if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { *rc = -ENODEV; - DBG("KW: NAK on address\n"); + DBG_LOW("KW: NAK on address\n"); return state_stop; } else { if (rw) { state = state_read; if (*len > 1) - kw_write_reg(reg_control, KW_I2C_CTL_AAK); + kw_write_reg(reg_control, + KW_I2C_CTL_AAK); } else { state = state_write; kw_write_reg(reg_data, **data); @@ -250,7 +273,7 @@ static int kw_handle_interrupt(struct lo } else if (state == state_write) { ack = kw_read_reg(reg_status); if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { - DBG("KW: nack on data write\n"); + DBG_LOW("KW: nack on data write\n"); *rc = -EIO; goto stop; } else if (*len) { @@ -291,35 +314,57 @@ static int kw_handle_interrupt(struct lo return state_stop; } -static int keywest_low_i2c_func(struct low_i2c_host *host, u8 addr, u8 subaddr, u8 *data, int len) +static int kw_i2c_open(struct pmac_i2c_bus *bus) +{ + struct pmac_i2c_host_kw *host = bus->hostdata; + down(&host->mutex); + return 0; +} + +static void kw_i2c_close(struct pmac_i2c_bus *bus) { + struct pmac_i2c_host_kw *host = bus->hostdata; + up(&host->mutex); +} + +static int kw_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len) +{ + struct pmac_i2c_host_kw *host = bus->hostdata; u8 mode_reg = host->speed; int state = state_addr; int rc = 0; /* Setup mode & subaddress if any */ - switch(host->mode) { - case pmac_low_i2c_mode_dumb: - printk(KERN_ERR "low_i2c: Dumb mode not supported !\n"); + switch(bus->mode) { + case pmac_i2c_mode_dumb: return -EINVAL; - case pmac_low_i2c_mode_std: + case pmac_i2c_mode_std: mode_reg |= KW_I2C_MODE_STANDARD; + if (subsize != 0) + return -EINVAL; break; - case pmac_low_i2c_mode_stdsub: + case pmac_i2c_mode_stdsub: mode_reg |= KW_I2C_MODE_STANDARDSUB; + if (subsize != 1) + return -EINVAL; break; - case pmac_low_i2c_mode_combined: + case pmac_i2c_mode_combined: mode_reg |= KW_I2C_MODE_COMBINED; + if (subsize != 1) + return -EINVAL; break; } /* Setup channel & clear pending irqs */ kw_write_reg(reg_isr, kw_read_reg(reg_isr)); - kw_write_reg(reg_mode, mode_reg | (host->channel << 4)); + kw_write_reg(reg_mode, mode_reg | (bus->channel << 4)); kw_write_reg(reg_status, 0); - /* Set up address and r/w bit */ - kw_write_reg(reg_addr, addr); + /* Set up address and r/w bit, strip possible stale bus number from + * address top bits + */ + kw_write_reg(reg_addr, addrdir & 0xff); /* Set up the sub address */ if ((mode_reg & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_STANDARDSUB @@ -330,27 +375,27 @@ static int keywest_low_i2c_func(struct l kw_write_reg(reg_ier, 0 /*KW_I2C_IRQ_MASK*/); kw_write_reg(reg_control, KW_I2C_CTL_XADDR); - /* State machine, to turn into an interrupt handler */ + /* State machine, to turn into an interrupt handler in the future */ while(state != state_idle) { - u8 isr = kw_wait_interrupt(host); - state = kw_handle_interrupt(host, state, addr & 1, &rc, &data, &len, isr); + u8 isr = kw_i2c_wait_interrupt(bus); + state = kw_i2c_handle_interrupt(bus, state, addrdir & 1, &rc, + &data, &len, isr); } return rc; } -static void keywest_low_i2c_add(struct device_node *np) +static struct pmac_i2c_host_kw *__init kw_i2c_host_init(struct device_node *np) { - struct low_i2c_host *host = find_low_i2c_host(NULL); + struct pmac_i2c_host_kw *host; u32 *psteps, *prate, *addrp, steps; - struct device_node *parent; + host = kzalloc(sizeof(struct pmac_i2c_host_kw), GFP_KERNEL); if (host == NULL) { printk(KERN_ERR "low_i2c: Can't allocate host for %s\n", np->full_name); - return; + return NULL; } - memset(host, 0, sizeof(*host)); /* Apple is kind enough to provide a valid AAPL,address property * on all i2c keywest nodes so far ... we would have to fallback @@ -360,18 +405,14 @@ static void keywest_low_i2c_add(struct d if (addrp == NULL) { printk(KERN_ERR "low_i2c: Can't find address for %s\n", np->full_name); - return; + kfree(host); + return NULL; } init_MUTEX(&host->mutex); - host->np = of_node_get(np); psteps = (u32 *)get_property(np, "AAPL,address-step", NULL); steps = psteps ? (*psteps) : 0x10; for (host->bsteps = 0; (steps & 0x01) == 0; host->bsteps++) steps >>= 1; - parent = of_get_parent(np); - host->num_channels = 1; - if (parent && parent->name[0] == 'u') - host->num_channels = 2; /* Select interface rate */ host->speed = KW_I2C_MODE_25KHZ; prate = (u32 *)get_property(np, "AAPL,i2c-rate", NULL); @@ -387,148 +428,620 @@ static void keywest_low_i2c_add(struct d break; } - printk(KERN_INFO "low_i2c: Bus %s found at 0x%08x, %d channels," - " speed = %d KHz\n", - np->full_name, *addrp, host->num_channels, prate ? *prate : 25); - - host->mode = pmac_low_i2c_mode_std; + printk(KERN_INFO "KeyWest i2c @0x%08x %s\n", *addrp, np->full_name); host->base = ioremap((*addrp), 0x1000); - host->func = keywest_low_i2c_func; + + return host; +} + + +static void __init kw_i2c_add(struct pmac_i2c_host_kw *host, + struct device_node *controller, + struct device_node *busnode, + int channel) +{ + struct pmac_i2c_bus *bus; + + bus = kzalloc(sizeof(struct pmac_i2c_bus), GFP_KERNEL); + if (bus == NULL) + return; + + bus->controller = of_node_get(controller); + bus->busnode = of_node_get(busnode); + bus->type = pmac_i2c_bus_keywest; + bus->hostdata = host; + bus->channel = channel; + bus->mode = pmac_i2c_mode_std; + bus->open = kw_i2c_open; + bus->close = kw_i2c_close; + bus->xfer = kw_i2c_xfer; + init_MUTEX(&bus->sem); + if (controller == busnode) + bus->flags = pmac_i2c_multibus; + list_add(&bus->link, &pmac_i2c_busses); + + printk(KERN_INFO " channel %d bus %s\n", channel, + (controller == busnode) ? "" : busnode->full_name); +} + +static void __init kw_i2c_probe(void) +{ + struct device_node *np, *child, *parent; + + /* Probe keywest-i2c busses */ + for (np = NULL; + (np = of_find_compatible_node(np, "i2c","keywest-i2c")) != NULL;){ + struct pmac_i2c_host_kw *host; + int multibus, chans, i; + + /* Found one, init a host structure */ + host = kw_i2c_host_init(np); + if (host == NULL) + continue; + + /* Now check if we have a multibus setup (old style) or if we + * have proper bus nodes. Note that the "new" way (proper bus + * nodes) might cause us to not create some busses that are + * kept hidden in the device-tree. In the future, we might + * want to work around that by creating busses without a node + * but not for now + */ + child = of_get_next_child(np, NULL); + multibus = !child || strcmp(child->name, "i2c-bus"); + of_node_put(child); + + /* For a multibus setup, we get the bus count based on the + * parent type + */ + if (multibus) { + parent = of_get_parent(np); + if (parent == NULL) + continue; + chans = parent->name[0] == 'u' ? 2 : 1; + for (i = 0; i < chans; i++) + kw_i2c_add(host, np, np, i); + } else { + for (child = NULL; + (child = of_get_next_child(np, child)) != NULL;) { + u32 *reg = + (u32 *)get_property(child, "reg", NULL); + if (reg == NULL) + continue; + kw_i2c_add(host, np, child, *reg); + } + } + } } + /* * * PMU implementation * */ - #ifdef CONFIG_ADB_PMU -static int pmu_low_i2c_func(struct low_i2c_host *host, u8 addr, u8 sub, u8 *data, int len) +/* + * i2c command block to the PMU + */ +struct pmu_i2c_hdr { + u8 bus; + u8 mode; + u8 bus2; + u8 address; + u8 sub_addr; + u8 comb_addr; + u8 count; + u8 data[]; +}; + +static void pmu_i2c_complete(struct adb_request *req) { - // TODO - return -ENODEV; + complete(req->arg); } -static void pmu_low_i2c_add(struct device_node *np) +static int pmu_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len) { - struct low_i2c_host *host = find_low_i2c_host(NULL); + struct adb_request *req = bus->hostdata; + struct pmu_i2c_hdr *hdr = (struct pmu_i2c_hdr *)&req->data[1]; + struct completion comp; + int read = addrdir & 1; + int retry; + int rc = 0; - if (host == NULL) { - printk(KERN_ERR "low_i2c: Can't allocate host for %s\n", - np->full_name); - return; + /* For now, limit ourselves to 16 bytes transfers */ + if (len > 16) + return -EINVAL; + + init_completion(&comp); + + for (retry = 0; retry < 16; retry++) { + memset(req, 0, sizeof(struct adb_request)); + hdr->bus = bus->channel; + hdr->count = len; + + switch(bus->mode) { + case pmac_i2c_mode_std: + if (subsize != 0) + return -EINVAL; + hdr->address = addrdir; + hdr->mode = PMU_I2C_MODE_SIMPLE; + break; + case pmac_i2c_mode_stdsub: + case pmac_i2c_mode_combined: + if (subsize != 1) + return -EINVAL; + hdr->address = addrdir & 0xfe; + hdr->comb_addr = addrdir; + hdr->sub_addr = subaddr; + if (bus->mode == pmac_i2c_mode_stdsub) + hdr->mode = PMU_I2C_MODE_STDSUB; + else + hdr->mode = PMU_I2C_MODE_COMBINED; + break; + default: + return -EINVAL; + } + + INIT_COMPLETION(comp); + req->data[0] = PMU_I2C_CMD; + req->reply[0] = 0xff; + req->nbytes = sizeof(struct pmu_i2c_hdr) + 1; + req->done = pmu_i2c_complete; + req->arg = ∁ + if (!read) { + memcpy(hdr->data, data, len); + req->nbytes += len; + } + rc = pmu_queue_request(req); + if (rc) + return rc; + wait_for_completion(&comp); + if (req->reply[0] == PMU_I2C_STATUS_OK) + break; + msleep(15); } - memset(host, 0, sizeof(*host)); + if (req->reply[0] != PMU_I2C_STATUS_OK) + return -EIO; - init_MUTEX(&host->mutex); - host->np = of_node_get(np); - host->num_channels = 3; - host->mode = pmac_low_i2c_mode_std; - host->func = pmu_low_i2c_func; + for (retry = 0; retry < 16; retry++) { + memset(req, 0, sizeof(struct adb_request)); + + /* I know that looks like a lot, slow as hell, but darwin + * does it so let's be on the safe side for now + */ + msleep(15); + + hdr->bus = PMU_I2C_BUS_STATUS; + + INIT_COMPLETION(comp); + req->data[0] = PMU_I2C_CMD; + req->reply[0] = 0xff; + req->nbytes = 2; + req->done = pmu_i2c_complete; + req->arg = ∁ + rc = pmu_queue_request(req); + if (rc) + return rc; + wait_for_completion(&comp); + + if (req->reply[0] == PMU_I2C_STATUS_OK && !read) + return 0; + if (req->reply[0] == PMU_I2C_STATUS_DATAREAD && read) { + int rlen = req->reply_len - 1; + + if (rlen != len) { + printk(KERN_WARNING "low_i2c: PMU returned %d" + " bytes, expected %d !\n", rlen, len); + return -EIO; + } + memcpy(data, &req->reply[1], len); + return 0; + } + } + return -EIO; +} + +static void __init pmu_i2c_probe(void) +{ + struct pmac_i2c_bus *bus; + struct device_node *busnode; + int channel, sz; + + if (!pmu_present()) + return; + + /* There might or might not be a "pmu-i2c" node, we use that + * or via-pmu itself, whatever we find. I haven't seen a machine + * with separate bus nodes, so we assume a multibus setup + */ + busnode = of_find_node_by_name(NULL, "pmu-i2c"); + if (busnode == NULL) + busnode = of_find_node_by_name(NULL, "via-pmu"); + if (busnode == NULL) + return; + + printk(KERN_INFO "PMU i2c %s\n", busnode->full_name); + + /* + * We add bus 1 and 2 only for now, bus 0 is "special" + */ + for (channel = 1; channel <= 2; channel++) { + sz = sizeof(struct pmac_i2c_bus) + sizeof(struct adb_request); + bus = kzalloc(sz, GFP_KERNEL); + if (bus == NULL) + return; + + bus->controller = busnode; + bus->busnode = busnode; + bus->type = pmac_i2c_bus_pmu; + bus->channel = channel; + bus->mode = pmac_i2c_mode_std; + bus->hostdata = bus + 1; + bus->xfer = pmu_i2c_xfer; + init_MUTEX(&bus->sem); + bus->flags = pmac_i2c_multibus; + list_add(&bus->link, &pmac_i2c_busses); + + printk(KERN_INFO " channel %d bus \n", channel); + } } #endif /* CONFIG_ADB_PMU */ -void __init pmac_init_low_i2c(void) + +/* + * + * SMU implementation + * + */ + +#ifdef CONFIG_PMAC_SMU + +static void smu_i2c_complete(struct smu_i2c_cmd *cmd, void *misc) { - struct device_node *np; + complete(misc); +} - /* Probe keywest-i2c busses */ - np = of_find_compatible_node(NULL, "i2c", "keywest-i2c"); - while(np) { - keywest_low_i2c_add(np); - np = of_find_compatible_node(np, "i2c", "keywest-i2c"); +static int smu_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len) +{ + struct smu_i2c_cmd *cmd = bus->hostdata; + struct completion comp; + int read = addrdir & 1; + int rc = 0; + + memset(cmd, 0, sizeof(struct smu_i2c_cmd)); + cmd->info.bus = bus->channel; + cmd->info.devaddr = addrdir; + cmd->info.datalen = len; + + switch(bus->mode) { + case pmac_i2c_mode_std: + if (subsize != 0) + return -EINVAL; + cmd->info.type = SMU_I2C_TRANSFER_SIMPLE; + break; + case pmac_i2c_mode_stdsub: + case pmac_i2c_mode_combined: + if (subsize > 3 || subsize < 1) + return -EINVAL; + cmd->info.sublen = subsize; + /* that's big-endian only but heh ! */ + memcpy(&cmd->info.subaddr, ((char *)&subaddr) + (4 - subsize), + subsize); + if (bus->mode == pmac_i2c_mode_stdsub) + cmd->info.type = SMU_I2C_TRANSFER_STDSUB; + else + cmd->info.type = SMU_I2C_TRANSFER_COMBINED; + break; + default: + return -EINVAL; } + if (!read) + memcpy(cmd->info.data, data, len); -#ifdef CONFIG_ADB_PMU - /* Probe PMU busses */ - np = of_find_node_by_name(NULL, "via-pmu"); - if (np) - pmu_low_i2c_add(np); -#endif /* CONFIG_ADB_PMU */ + init_completion(&comp); + cmd->done = smu_i2c_complete; + cmd->misc = ∁ + rc = smu_queue_i2c(cmd); + if (rc < 0) + return rc; + wait_for_completion(&comp); + rc = cmd->status; + + if (read) + memcpy(data, cmd->info.data, len); + return rc < 0 ? rc : 0; +} + +static void __init smu_i2c_probe(void) +{ + struct device_node *controller, *busnode; + struct pmac_i2c_bus *bus; + u32 *reg; + int sz; + + if (!smu_present()) + return; + + controller = of_find_node_by_name(NULL, "smu_i2c_control"); + if (controller == NULL) + controller = of_find_node_by_name(NULL, "smu"); + if (controller == NULL) + return; + + printk(KERN_INFO "SMU i2c %s\n", controller->full_name); + + /* Look for childs, note that they might not be of the right + * type as older device trees mix i2c busses and other thigns + * at the same level + */ + for (busnode = NULL; + (busnode = of_get_next_child(controller, busnode)) != NULL;) { + if (strcmp(busnode->type, "i2c") && + strcmp(busnode->type, "i2c-bus")) + continue; + reg = (u32 *)get_property(busnode, "reg", NULL); + if (reg == NULL) + continue; + + sz = sizeof(struct pmac_i2c_bus) + sizeof(struct smu_i2c_cmd); + bus = kzalloc(sz, GFP_KERNEL); + if (bus == NULL) + return; + + bus->controller = controller; + bus->busnode = of_node_get(busnode); + bus->type = pmac_i2c_bus_smu; + bus->channel = *reg; + bus->mode = pmac_i2c_mode_std; + bus->hostdata = bus + 1; + bus->xfer = smu_i2c_xfer; + init_MUTEX(&bus->sem); + bus->flags = 0; + list_add(&bus->link, &pmac_i2c_busses); + + printk(KERN_INFO " channel %x bus %s\n", + bus->channel, busnode->full_name); + } +} + +#endif /* CONFIG_PMAC_SMU */ + +/* + * + * Core code + * + */ + + +struct pmac_i2c_bus *pmac_i2c_find_bus(struct device_node *node) +{ + struct device_node *p = of_node_get(node); + struct device_node *prev = NULL; + struct pmac_i2c_bus *bus; + + while(p) { + list_for_each_entry(bus, &pmac_i2c_busses, link) { + if (p == bus->busnode) { + if (prev && bus->flags & pmac_i2c_multibus) { + u32 *reg; + reg = (u32 *)get_property(prev, "reg", + NULL); + if (!reg) + continue; + if (((*reg) >> 8) != bus->channel) + continue; + } + of_node_put(p); + of_node_put(prev); + return bus; + } + } + of_node_put(prev); + prev = p; + p = of_get_parent(p); + } + return NULL; +} +EXPORT_SYMBOL_GPL(pmac_i2c_find_bus); + +u8 pmac_i2c_get_dev_addr(struct device_node *device) +{ + u32 *reg = (u32 *)get_property(device, "reg", NULL); + + if (reg == NULL) + return 0; + + return (*reg) & 0xff; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_dev_addr); + +struct device_node *pmac_i2c_get_controller(struct pmac_i2c_bus *bus) +{ + return bus->controller; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_controller); + +struct device_node *pmac_i2c_get_bus_node(struct pmac_i2c_bus *bus) +{ + return bus->busnode; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_bus_node); + +int pmac_i2c_get_type(struct pmac_i2c_bus *bus) +{ + return bus->type; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_type); + +int pmac_i2c_get_flags(struct pmac_i2c_bus *bus) +{ + return bus->flags; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_flags); + +void pmac_i2c_attach_adapter(struct pmac_i2c_bus *bus, + struct i2c_adapter *adapter) +{ + WARN_ON(bus->adapter != NULL); + bus->adapter = adapter; +} +EXPORT_SYMBOL_GPL(pmac_i2c_attach_adapter); + +void pmac_i2c_detach_adapter(struct pmac_i2c_bus *bus, + struct i2c_adapter *adapter) +{ + WARN_ON(bus->adapter != adapter); + bus->adapter = NULL; +} +EXPORT_SYMBOL_GPL(pmac_i2c_detach_adapter); + +struct i2c_adapter *pmac_i2c_get_adapter(struct pmac_i2c_bus *bus) +{ + return bus->adapter; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_adapter); + +extern int pmac_i2c_match_adapter(struct device_node *dev, + struct i2c_adapter *adapter) +{ + struct pmac_i2c_bus *bus = pmac_i2c_find_bus(dev); - /* TODO: Add CUDA support as well */ + if (bus == NULL) + return 0; + return (bus->adapter == adapter); } +EXPORT_SYMBOL_GPL(pmac_i2c_match_adapter); int pmac_low_i2c_lock(struct device_node *np) { - struct low_i2c_host *host = find_low_i2c_host(np); + struct pmac_i2c_bus *bus, *found = NULL; - if (!host) + list_for_each_entry(bus, &pmac_i2c_busses, link) { + if (np == bus->controller) { + found = bus; + break; + } + } + if (!found) return -ENODEV; - down(&host->mutex); - return 0; + return pmac_i2c_open(bus, 0); } -EXPORT_SYMBOL(pmac_low_i2c_lock); +EXPORT_SYMBOL_GPL(pmac_low_i2c_lock); int pmac_low_i2c_unlock(struct device_node *np) { - struct low_i2c_host *host = find_low_i2c_host(np); + struct pmac_i2c_bus *bus, *found = NULL; - if (!host) + list_for_each_entry(bus, &pmac_i2c_busses, link) { + if (np == bus->controller) { + found = bus; + break; + } + } + if (!found) return -ENODEV; - up(&host->mutex); + pmac_i2c_close(bus); return 0; } -EXPORT_SYMBOL(pmac_low_i2c_unlock); +EXPORT_SYMBOL_GPL(pmac_low_i2c_unlock); -int pmac_low_i2c_open(struct device_node *np, int channel) +int pmac_i2c_open(struct pmac_i2c_bus *bus, int polled) { - struct low_i2c_host *host = find_low_i2c_host(np); - - if (!host) - return -ENODEV; - - if (channel >= host->num_channels) - return -EINVAL; - - down(&host->mutex); - host->is_open = 1; - host->channel = channel; + int rc; + down(&bus->sem); + bus->polled = polled; + bus->opened = 1; + bus->mode = pmac_i2c_mode_std; + if (bus->open && (rc = bus->open(bus)) != 0) { + bus->opened = 0; + up(&bus->sem); + return rc; + } return 0; } -EXPORT_SYMBOL(pmac_low_i2c_open); +EXPORT_SYMBOL_GPL(pmac_i2c_open); -int pmac_low_i2c_close(struct device_node *np) +void pmac_i2c_close(struct pmac_i2c_bus *bus) { - struct low_i2c_host *host = find_low_i2c_host(np); + WARN_ON(!bus->opened); + if (bus->close) + bus->close(bus); + bus->opened = 0; + up(&bus->sem); +} +EXPORT_SYMBOL_GPL(pmac_i2c_close); - if (!host) - return -ENODEV; +int pmac_i2c_setmode(struct pmac_i2c_bus *bus, int mode) +{ + WARN_ON(!bus->opened); - host->is_open = 0; - up(&host->mutex); + /* Report me if you see the error below as there might be a new + * "combined4" mode that I need to implement for the SMU bus + */ + if (mode < pmac_i2c_mode_dumb || mode > pmac_i2c_mode_combined) { + printk(KERN_ERR "low_i2c: Invalid mode %d requested on" + " bus %s !\n", mode, bus->busnode->full_name); + return -EINVAL; + } + bus->mode = mode; return 0; } -EXPORT_SYMBOL(pmac_low_i2c_close); +EXPORT_SYMBOL_GPL(pmac_i2c_setmode); -int pmac_low_i2c_setmode(struct device_node *np, int mode) +int pmac_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len) { - struct low_i2c_host *host = find_low_i2c_host(np); + int rc; - if (!host) - return -ENODEV; - WARN_ON(!host->is_open); - host->mode = mode; + WARN_ON(!bus->opened); - return 0; + DBG("xfer() chan=%d, addrdir=0x%x, mode=%d, subsize=%d, subaddr=0x%x," + " %d bytes, bus %s\n", bus->channel, addrdir, bus->mode, subsize, + subaddr, len, bus->busnode->full_name); + + rc = bus->xfer(bus, addrdir, subsize, subaddr, data, len); + +#ifdef DEBUG + if (rc) + DBG("xfer error %d\n", rc); +#endif + return rc; } -EXPORT_SYMBOL(pmac_low_i2c_setmode); +EXPORT_SYMBOL_GPL(pmac_i2c_xfer); -int pmac_low_i2c_xfer(struct device_node *np, u8 addrdir, u8 subaddr, u8 *data, int len) +/* + * Initialize us: probe all i2c busses on the machine and instantiate + * busses. + */ +/* This is non-static as it might be called early by smp code */ +int __init pmac_i2c_init(void) { - struct low_i2c_host *host = find_low_i2c_host(np); + static int i2c_inited; - if (!host) - return -ENODEV; - WARN_ON(!host->is_open); + if (i2c_inited) + return 0; + i2c_inited = 1; + + /* Probe keywest-i2c busses */ + kw_i2c_probe(); - return host->func(host, addrdir, subaddr, data, len); +#ifdef CONFIG_ADB_PMU + pmu_i2c_probe(); +#endif + +#ifdef CONFIG_PMAC_SMU + smu_i2c_probe(); +#endif + + return 0; } -EXPORT_SYMBOL(pmac_low_i2c_xfer); +arch_initcall(pmac_i2c_init); Index: linux-work/include/asm-powerpc/pmac_low_i2c.h =================================================================== --- linux-work.orig/include/asm-powerpc/pmac_low_i2c.h 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/include/asm-powerpc/pmac_low_i2c.h 2006-01-07 11:25:18.000000000 +1100 @@ -15,30 +15,87 @@ /* i2c mode (based on the platform functions format) */ enum { - pmac_low_i2c_mode_dumb = 1, - pmac_low_i2c_mode_std = 2, - pmac_low_i2c_mode_stdsub = 3, - pmac_low_i2c_mode_combined = 4, + pmac_i2c_mode_dumb = 1, + pmac_i2c_mode_std = 2, + pmac_i2c_mode_stdsub = 3, + pmac_i2c_mode_combined = 4, }; /* RW bit in address */ enum { - pmac_low_i2c_read = 0x01, - pmac_low_i2c_write = 0x00 + pmac_i2c_read = 0x01, + pmac_i2c_write = 0x00 }; +/* i2c bus type */ +enum { + pmac_i2c_bus_keywest = 0, + pmac_i2c_bus_pmu = 1, + pmac_i2c_bus_smu = 2, +}; + +/* i2c bus features */ +enum { + /* can_largesub : supports >1 byte subaddresses (SMU only) */ + pmac_i2c_can_largesub = 0x00000001u, + + /* multibus : device node holds multiple busses, bus number is + * encoded in bits 0xff00 of "reg" of a given device + */ + pmac_i2c_multibus = 0x00000002u, +}; + +/* i2c busses in the system */ +struct pmac_i2c_bus; +struct i2c_adapter; + /* Init, called early during boot */ -extern void pmac_init_low_i2c(void); +extern int pmac_i2c_init(void); -/* Locking functions exposed to i2c-keywest */ -int pmac_low_i2c_lock(struct device_node *np); -int pmac_low_i2c_unlock(struct device_node *np); +/* Lookup an i2c bus for a device-node. The node can be either the bus + * node itself or a device below it. In the case of a multibus, the bus + * node itself is the controller node, else, it's a child of the controller + * node + */ +extern struct pmac_i2c_bus *pmac_i2c_find_bus(struct device_node *node); + +/* Get the address for an i2c device. This strips the bus number if + * necessary. The 7 bits address is returned 1 bit right shifted so that the + * direction can be directly ored in + */ +extern u8 pmac_i2c_get_dev_addr(struct device_node *device); + +/* Get infos about a bus */ +extern struct device_node *pmac_i2c_get_controller(struct pmac_i2c_bus *bus); +extern struct device_node *pmac_i2c_get_bus_node(struct pmac_i2c_bus *bus); +extern int pmac_i2c_get_type(struct pmac_i2c_bus *bus); +extern int pmac_i2c_get_flags(struct pmac_i2c_bus *bus); + +/* i2c layer adapter attach/detach */ +extern void pmac_i2c_attach_adapter(struct pmac_i2c_bus *bus, + struct i2c_adapter *adapter); +extern void pmac_i2c_detach_adapter(struct pmac_i2c_bus *bus, + struct i2c_adapter *adapter); +extern struct i2c_adapter *pmac_i2c_get_adapter(struct pmac_i2c_bus *bus); + +/* March a device or bus with an i2c adapter structure, to be used by drivers + * to match device-tree nodes with i2c adapters during adapter discovery + * callbacks + */ +extern int pmac_i2c_match_adapter(struct device_node *dev, + struct i2c_adapter *adapter); + + +/* (legacy) Locking functions exposed to i2c-keywest */ +extern int pmac_low_i2c_lock(struct device_node *np); +extern int pmac_low_i2c_unlock(struct device_node *np); /* Access functions for platform code */ -int pmac_low_i2c_open(struct device_node *np, int channel); -int pmac_low_i2c_close(struct device_node *np); -int pmac_low_i2c_setmode(struct device_node *np, int mode); -int pmac_low_i2c_xfer(struct device_node *np, u8 addrdir, u8 subaddr, u8 *data, int len); +extern int pmac_i2c_open(struct pmac_i2c_bus *bus, int polled); +extern void pmac_i2c_close(struct pmac_i2c_bus *bus); +extern int pmac_i2c_setmode(struct pmac_i2c_bus *bus, int mode); +extern int pmac_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, + u32 subaddr, u8 *data, int len); #endif /* __KERNEL__ */ Index: linux-work/arch/powerpc/platforms/powermac/smp.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/smp.c 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/smp.c 2006-01-07 11:25:16.000000000 +1100 @@ -482,7 +482,7 @@ static void __devinit smp_core99_take_ti /* * G5s enable/disable the timebase via an i2c-connected clock chip. */ -static struct device_node *pmac_tb_clock_chip_host; +static struct pmac_i2c_bus *pmac_tb_clock_chip_host; static u8 pmac_tb_pulsar_addr; static void smp_core99_cypress_tb_freeze(int freeze) @@ -493,20 +493,20 @@ static void smp_core99_cypress_tb_freeze /* Strangely, the device-tree says address is 0xd2, but darwin * accesses 0xd0 ... */ - pmac_low_i2c_setmode(pmac_tb_clock_chip_host, - pmac_low_i2c_mode_combined); - rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host, - 0xd0 | pmac_low_i2c_read, - 0x81, &data, 1); + pmac_i2c_setmode(pmac_tb_clock_chip_host, + pmac_i2c_mode_combined); + rc = pmac_i2c_xfer(pmac_tb_clock_chip_host, + 0xd0 | pmac_i2c_read, + 1, 0x81, &data, 1); if (rc != 0) goto bail; data = (data & 0xf3) | (freeze ? 0x00 : 0x0c); - pmac_low_i2c_setmode(pmac_tb_clock_chip_host, pmac_low_i2c_mode_stdsub); - rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host, - 0xd0 | pmac_low_i2c_write, - 0x81, &data, 1); + pmac_i2c_setmode(pmac_tb_clock_chip_host, pmac_i2c_mode_stdsub); + rc = pmac_i2c_xfer(pmac_tb_clock_chip_host, + 0xd0 | pmac_i2c_write, + 1, 0x81, &data, 1); bail: if (rc != 0) { @@ -522,20 +522,20 @@ static void smp_core99_pulsar_tb_freeze( u8 data; int rc; - pmac_low_i2c_setmode(pmac_tb_clock_chip_host, - pmac_low_i2c_mode_combined); - rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host, - pmac_tb_pulsar_addr | pmac_low_i2c_read, - 0x2e, &data, 1); + pmac_i2c_setmode(pmac_tb_clock_chip_host, + pmac_i2c_mode_combined); + rc = pmac_i2c_xfer(pmac_tb_clock_chip_host, + pmac_tb_pulsar_addr | pmac_i2c_read, + 1, 0x2e, &data, 1); if (rc != 0) goto bail; data = (data & 0x88) | (freeze ? 0x11 : 0x22); - pmac_low_i2c_setmode(pmac_tb_clock_chip_host, pmac_low_i2c_mode_stdsub); - rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host, - pmac_tb_pulsar_addr | pmac_low_i2c_write, - 0x2e, &data, 1); + pmac_i2c_setmode(pmac_tb_clock_chip_host, pmac_i2c_mode_stdsub); + rc = pmac_i2c_xfer(pmac_tb_clock_chip_host, + pmac_tb_pulsar_addr | pmac_i2c_write, + 1, 0x2e, &data, 1); bail: if (rc != 0) { printk(KERN_ERR "Pulsar Timebase %s rc: %d\n", @@ -560,13 +560,15 @@ static void __init smp_core99_setup_i2c_ if (!ok) continue; + pmac_tb_clock_chip_host = pmac_i2c_find_bus(cc); + if (pmac_tb_clock_chip_host == NULL) + continue; reg = (u32 *)get_property(cc, "reg", NULL); if (reg == NULL) continue; - switch (*reg) { case 0xd2: - if (device_is_compatible(cc, "pulsar-legacy-slewing")) { + if (device_is_compatible(cc,"pulsar-legacy-slewing")) { pmac_tb_freeze = smp_core99_pulsar_tb_freeze; pmac_tb_pulsar_addr = 0xd2; name = "Pulsar"; @@ -585,30 +587,19 @@ static void __init smp_core99_setup_i2c_ break; } if (pmac_tb_freeze != NULL) { - struct device_node *p = of_get_parent(cc); - of_node_put(cc); - while(p && strcmp(p->type, "i2c")) { - cc = of_get_parent(p); - of_node_put(p); - p = cc; - } - if (p == NULL) - goto no_i2c_sync; /* Open i2c bus for synchronous access */ - if (pmac_low_i2c_open(p, 0)) { - printk(KERN_ERR "Failed top open i2c bus %s for clock" - " sync, fallback to software sync !\n", - p->full_name); - of_node_put(p); + if (pmac_i2c_open(pmac_tb_clock_chip_host, 1)) { + printk(KERN_ERR "Failed top open i2c bus for clock" + " sync, fallback to software sync !\n"); goto no_i2c_sync; } - pmac_tb_clock_chip_host = p; printk(KERN_INFO "Processor timebase sync using %s i2c clock\n", name); return; } no_i2c_sync: pmac_tb_freeze = NULL; + pmac_tb_clock_chip_host = NULL; } #endif /* CONFIG_PPC64 */ @@ -752,8 +743,18 @@ static int __init smp_core99_probe(void) if (ncpus <= 1) return 1; + /* We need to perform some early initialisations before we can start + * setting up SMP as we are running before initcalls + */ + pmac_i2c_init(); + + /* Setup various bits like timebase sync method, ability to nap, ... */ smp_core99_setup(ncpus); + + /* Install IPIs */ mpic_request_ipis(); + + /* Collect l2cr and l3cr values from CPU 0 */ core99_init_caches(0); return ncpus; @@ -817,7 +818,7 @@ static void __devinit smp_core99_setup_c /* Close i2c bus if it was used for tb sync */ if (pmac_tb_clock_chip_host) { - pmac_low_i2c_close(pmac_tb_clock_chip_host); + pmac_i2c_close(pmac_tb_clock_chip_host); pmac_tb_clock_chip_host = NULL; } Index: linux-work/drivers/macintosh/via-pmu.c =================================================================== --- linux-work.orig/drivers/macintosh/via-pmu.c 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/drivers/macintosh/via-pmu.c 2006-01-07 11:25:16.000000000 +1100 @@ -197,7 +197,6 @@ static int pmu_adb_reset_bus(void); #endif /* CONFIG_ADB */ static int init_pmu(void); -static int pmu_queue_request(struct adb_request *req); static void pmu_start(void); static irqreturn_t via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs); static irqreturn_t gpio1_interrupt(int irq, void *arg, struct pt_regs *regs); @@ -1802,258 +1801,6 @@ pmu_present(void) return via != 0; } -struct pmu_i2c_hdr { - u8 bus; - u8 mode; - u8 bus2; - u8 address; - u8 sub_addr; - u8 comb_addr; - u8 count; -}; - -int -pmu_i2c_combined_read(int bus, int addr, int subaddr, u8* data, int len) -{ - struct adb_request req; - struct pmu_i2c_hdr *hdr = (struct pmu_i2c_hdr *)&req.data[1]; - int retry; - int rc; - - for (retry=0; retry<16; retry++) { - memset(&req, 0, sizeof(req)); - - hdr->bus = bus; - hdr->address = addr & 0xfe; - hdr->mode = PMU_I2C_MODE_COMBINED; - hdr->bus2 = 0; - hdr->sub_addr = subaddr; - hdr->comb_addr = addr | 1; - hdr->count = len; - - req.nbytes = sizeof(struct pmu_i2c_hdr) + 1; - req.reply_expected = 0; - req.reply_len = 0; - req.data[0] = PMU_I2C_CMD; - req.reply[0] = 0xff; - rc = pmu_queue_request(&req); - if (rc) - return rc; - while(!req.complete) - pmu_poll(); - if (req.reply[0] == PMU_I2C_STATUS_OK) - break; - mdelay(15); - } - if (req.reply[0] != PMU_I2C_STATUS_OK) - return -1; - - for (retry=0; retry<16; retry++) { - memset(&req, 0, sizeof(req)); - - mdelay(15); - - hdr->bus = PMU_I2C_BUS_STATUS; - req.reply[0] = 0xff; - - req.nbytes = 2; - req.reply_expected = 0; - req.reply_len = 0; - req.data[0] = PMU_I2C_CMD; - rc = pmu_queue_request(&req); - if (rc) - return rc; - while(!req.complete) - pmu_poll(); - if (req.reply[0] == PMU_I2C_STATUS_DATAREAD) { - memcpy(data, &req.reply[1], req.reply_len - 1); - return req.reply_len - 1; - } - } - return -1; -} - -int -pmu_i2c_stdsub_write(int bus, int addr, int subaddr, u8* data, int len) -{ - struct adb_request req; - struct pmu_i2c_hdr *hdr = (struct pmu_i2c_hdr *)&req.data[1]; - int retry; - int rc; - - for (retry=0; retry<16; retry++) { - memset(&req, 0, sizeof(req)); - - hdr->bus = bus; - hdr->address = addr & 0xfe; - hdr->mode = PMU_I2C_MODE_STDSUB; - hdr->bus2 = 0; - hdr->sub_addr = subaddr; - hdr->comb_addr = addr & 0xfe; - hdr->count = len; - - req.data[0] = PMU_I2C_CMD; - memcpy(&req.data[sizeof(struct pmu_i2c_hdr) + 1], data, len); - req.nbytes = sizeof(struct pmu_i2c_hdr) + len + 1; - req.reply_expected = 0; - req.reply_len = 0; - req.reply[0] = 0xff; - rc = pmu_queue_request(&req); - if (rc) - return rc; - while(!req.complete) - pmu_poll(); - if (req.reply[0] == PMU_I2C_STATUS_OK) - break; - mdelay(15); - } - if (req.reply[0] != PMU_I2C_STATUS_OK) - return -1; - - for (retry=0; retry<16; retry++) { - memset(&req, 0, sizeof(req)); - - mdelay(15); - - hdr->bus = PMU_I2C_BUS_STATUS; - req.reply[0] = 0xff; - - req.nbytes = 2; - req.reply_expected = 0; - req.reply_len = 0; - req.data[0] = PMU_I2C_CMD; - rc = pmu_queue_request(&req); - if (rc) - return rc; - while(!req.complete) - pmu_poll(); - if (req.reply[0] == PMU_I2C_STATUS_OK) - return len; - } - return -1; -} - -int -pmu_i2c_simple_read(int bus, int addr, u8* data, int len) -{ - struct adb_request req; - struct pmu_i2c_hdr *hdr = (struct pmu_i2c_hdr *)&req.data[1]; - int retry; - int rc; - - for (retry=0; retry<16; retry++) { - memset(&req, 0, sizeof(req)); - - hdr->bus = bus; - hdr->address = addr | 1; - hdr->mode = PMU_I2C_MODE_SIMPLE; - hdr->bus2 = 0; - hdr->sub_addr = 0; - hdr->comb_addr = 0; - hdr->count = len; - - req.data[0] = PMU_I2C_CMD; - req.nbytes = sizeof(struct pmu_i2c_hdr) + 1; - req.reply_expected = 0; - req.reply_len = 0; - req.reply[0] = 0xff; - rc = pmu_queue_request(&req); - if (rc) - return rc; - while(!req.complete) - pmu_poll(); - if (req.reply[0] == PMU_I2C_STATUS_OK) - break; - mdelay(15); - } - if (req.reply[0] != PMU_I2C_STATUS_OK) - return -1; - - for (retry=0; retry<16; retry++) { - memset(&req, 0, sizeof(req)); - - mdelay(15); - - hdr->bus = PMU_I2C_BUS_STATUS; - req.reply[0] = 0xff; - - req.nbytes = 2; - req.reply_expected = 0; - req.reply_len = 0; - req.data[0] = PMU_I2C_CMD; - rc = pmu_queue_request(&req); - if (rc) - return rc; - while(!req.complete) - pmu_poll(); - if (req.reply[0] == PMU_I2C_STATUS_DATAREAD) { - memcpy(data, &req.reply[1], req.reply_len - 1); - return req.reply_len - 1; - } - } - return -1; -} - -int -pmu_i2c_simple_write(int bus, int addr, u8* data, int len) -{ - struct adb_request req; - struct pmu_i2c_hdr *hdr = (struct pmu_i2c_hdr *)&req.data[1]; - int retry; - int rc; - - for (retry=0; retry<16; retry++) { - memset(&req, 0, sizeof(req)); - - hdr->bus = bus; - hdr->address = addr & 0xfe; - hdr->mode = PMU_I2C_MODE_SIMPLE; - hdr->bus2 = 0; - hdr->sub_addr = 0; - hdr->comb_addr = 0; - hdr->count = len; - - req.data[0] = PMU_I2C_CMD; - memcpy(&req.data[sizeof(struct pmu_i2c_hdr) + 1], data, len); - req.nbytes = sizeof(struct pmu_i2c_hdr) + len + 1; - req.reply_expected = 0; - req.reply_len = 0; - req.reply[0] = 0xff; - rc = pmu_queue_request(&req); - if (rc) - return rc; - while(!req.complete) - pmu_poll(); - if (req.reply[0] == PMU_I2C_STATUS_OK) - break; - mdelay(15); - } - if (req.reply[0] != PMU_I2C_STATUS_OK) - return -1; - - for (retry=0; retry<16; retry++) { - memset(&req, 0, sizeof(req)); - - mdelay(15); - - hdr->bus = PMU_I2C_BUS_STATUS; - req.reply[0] = 0xff; - - req.nbytes = 2; - req.reply_expected = 0; - req.reply_len = 0; - req.data[0] = PMU_I2C_CMD; - rc = pmu_queue_request(&req); - if (rc) - return rc; - while(!req.complete) - pmu_poll(); - if (req.reply[0] == PMU_I2C_STATUS_OK) - return len; - } - return -1; -} - #ifdef CONFIG_PM static LIST_HEAD(sleep_notifiers); @@ -2358,9 +2105,6 @@ pmac_suspend_devices(void) return -EBUSY; } - /* Disable clock spreading on some machines */ - pmac_tweak_clock_spreading(0); - /* Stop preemption */ preempt_disable(); @@ -2431,9 +2175,6 @@ pmac_wakeup_devices(void) mdelay(10); preempt_enable(); - /* Re-enable clock spreading on some machines */ - pmac_tweak_clock_spreading(1); - /* Resume devices */ device_resume(); @@ -3150,16 +2891,13 @@ static int __init init_pmu_sysfs(void) subsys_initcall(init_pmu_sysfs); EXPORT_SYMBOL(pmu_request); +EXPORT_SYMBOL(pmu_queue_request); EXPORT_SYMBOL(pmu_poll); EXPORT_SYMBOL(pmu_poll_adb); EXPORT_SYMBOL(pmu_wait_complete); EXPORT_SYMBOL(pmu_suspend); EXPORT_SYMBOL(pmu_resume); EXPORT_SYMBOL(pmu_unlock); -EXPORT_SYMBOL(pmu_i2c_combined_read); -EXPORT_SYMBOL(pmu_i2c_stdsub_write); -EXPORT_SYMBOL(pmu_i2c_simple_read); -EXPORT_SYMBOL(pmu_i2c_simple_write); #if defined(CONFIG_PM) && defined(CONFIG_PPC32) EXPORT_SYMBOL(pmu_enable_irled); EXPORT_SYMBOL(pmu_battery_count); Index: linux-work/include/asm-powerpc/pmac_feature.h =================================================================== --- linux-work.orig/include/asm-powerpc/pmac_feature.h 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/include/asm-powerpc/pmac_feature.h 2006-01-07 11:25:16.000000000 +1100 @@ -318,10 +318,6 @@ extern void pmac_register_agp_pm(struct extern void pmac_suspend_agp_for_card(struct pci_dev *dev); extern void pmac_resume_agp_for_card(struct pci_dev *dev); -/* Used by the via-pmu driver for suspend/resume - */ -extern void pmac_tweak_clock_spreading(int enable); - /* * The part below is for use by macio_asic.c only, do not rely * on the data structures or constants below in a normal driver Index: linux-work/include/linux/pmu.h =================================================================== --- linux-work.orig/include/linux/pmu.h 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/include/linux/pmu.h 2006-01-06 19:39:47.000000000 +1100 @@ -140,7 +140,7 @@ extern int find_via_pmu(void); extern int pmu_request(struct adb_request *req, void (*done)(struct adb_request *), int nbytes, ...); - +extern int pmu_queue_request(struct adb_request *req); extern void pmu_poll(void); extern void pmu_poll_adb(void); /* For use by xmon */ extern void pmu_wait_complete(struct adb_request *req); @@ -160,12 +160,6 @@ extern void pmu_unlock(void); extern int pmu_present(void); extern int pmu_get_model(void); -extern int pmu_i2c_combined_read(int bus, int addr, int subaddr, u8* data, int len); -extern int pmu_i2c_stdsub_write(int bus, int addr, int subaddr, u8* data, int len); -extern int pmu_i2c_simple_read(int bus, int addr, u8* data, int len); -extern int pmu_i2c_simple_write(int bus, int addr, u8* data, int len); - - #ifdef CONFIG_PM /* * Stuff for putting the powerbook to sleep and waking it again. Index: linux-work/arch/powerpc/platforms/powermac/setup.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/setup.c 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/setup.c 2006-01-07 11:25:18.000000000 +1100 @@ -658,27 +658,22 @@ static int __init pmac_declare_of_platfo { struct device_node *np, *npp; - np = find_devices("uni-n"); - if (np) { - for (np = np->child; np != NULL; np = np->sibling) - if (strncmp(np->name, "i2c", 3) == 0) { - of_platform_device_create(np, "uni-n-i2c", - NULL); - break; - } - } - np = find_devices("valkyrie"); + np = of_find_node_by_name(NULL, "valkyrie"); if (np) of_platform_device_create(np, "valkyrie", NULL); - np = find_devices("platinum"); + np = of_find_node_by_name(NULL, "platinum"); if (np) of_platform_device_create(np, "platinum", NULL); - - npp = of_find_node_by_name(NULL, "u3"); + npp = of_find_node_by_name(NULL, "uni-n"); + if (npp == NULL) + npp = of_find_node_by_name(NULL, "u3"); + if (npp == NULL) + npp = of_find_node_by_name(NULL, "u4"); if (npp) { for (np = NULL; (np = of_get_next_child(npp, np)) != NULL;) { if (strncmp(np->name, "i2c", 3) == 0) { - of_platform_device_create(np, "u3-i2c", NULL); + of_platform_device_create(np, "uni-n-i2c", + NULL); of_node_put(np); break; } Index: linux-work/drivers/i2c/busses/i2c-pmac-smu.c =================================================================== --- linux-work.orig/drivers/i2c/busses/i2c-pmac-smu.c 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/drivers/i2c/busses/i2c-pmac-smu.c 2006-01-07 11:25:18.000000000 +1100 @@ -103,8 +103,8 @@ static s32 smu_smbus_xfer( struct i2c_ad cmd.info.subaddr[1] = 0; cmd.info.subaddr[2] = 0; if (!read) { - cmd.info.data[0] = data->byte & 0xff; - cmd.info.data[1] = (data->byte >> 8) & 0xff; + cmd.info.data[0] = data->word & 0xff; + cmd.info.data[1] = (data->word >> 8) & 0xff; } break; /* Note that these are broken vs. the expected smbus API where @@ -116,7 +116,7 @@ static s32 smu_smbus_xfer( struct i2c_ad case I2C_SMBUS_BLOCK_DATA: cmd.info.type = SMU_I2C_TRANSFER_STDSUB; cmd.info.datalen = data->block[0] + 1; - if (cmd.info.datalen > 6) + if (cmd.info.datalen > (SMU_I2C_WRITE_MAX + 1)) return -EINVAL; if (!read) memcpy(cmd.info.data, data->block, cmd.info.datalen); @@ -273,7 +273,13 @@ static int dispose_iface(struct device * static int create_iface_of_platform(struct of_device* dev, const struct of_device_id *match) { - return create_iface(dev->node, &dev->dev); + struct device_node *node = dev->node; + + if (device_is_compatible(node, "smu-i2c") || + (node->parent != NULL && + device_is_compatible(node->parent, "smu-i2c-control"))) + return create_iface(node, &dev->dev); + return -ENODEV; } @@ -288,6 +294,9 @@ static struct of_device_id i2c_smu_match { .compatible = "smu-i2c", }, + { + .compatible = "i2c-bus", + }, {}, }; static struct of_platform_driver i2c_smu_of_platform_driver = Index: linux-work/drivers/macintosh/smu.c =================================================================== --- linux-work.orig/drivers/macintosh/smu.c 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/drivers/macintosh/smu.c 2006-01-07 11:25:18.000000000 +1100 @@ -94,6 +94,8 @@ struct smu_device { static struct smu_device *smu; static DECLARE_MUTEX(smu_part_access); +static void smu_i2c_retry(unsigned long data); + /* * SMU driver low level stuff */ @@ -469,7 +471,6 @@ int __init smu_init (void) smu->of_node = np; smu->db_irq = NO_IRQ; smu->msg_irq = NO_IRQ; - init_timer(&smu->i2c_timer); /* smu_cmdbuf_abs is in the low 2G of RAM, can be converted to a * 32 bits value safely @@ -544,6 +545,10 @@ static int smu_late_init(void) if (!smu) return 0; + init_timer(&smu->i2c_timer); + smu->i2c_timer.function = smu_i2c_retry; + smu->i2c_timer.data = (unsigned long)smu; + /* * Try to request the interrupts */ @@ -570,28 +575,41 @@ static int smu_late_init(void) return 0; } -arch_initcall(smu_late_init); +/* This has to be before arch_initcall as the low i2c stuff relies on the + * above having been done before we reach arch_initcalls + */ +core_initcall(smu_late_init); /* * sysfs visibility */ +static void smu_create_i2c(struct device_node *np) +{ + char name[32]; + u32 *reg = (u32 *)get_property(np, "reg", NULL); + + if (reg != NULL) { + sprintf(name, "smu-i2c-%02x", *reg); + of_platform_device_create(np, name, &smu->of_dev->dev); + } +} + static void smu_expose_childs(void *unused) { - struct device_node *np; + struct device_node *np, *gp; for (np = NULL; (np = of_get_next_child(smu->of_node, np)) != NULL;) { - if (device_is_compatible(np, "smu-i2c")) { - char name[32]; - u32 *reg = (u32 *)get_property(np, "reg", NULL); - - if (reg == NULL) - continue; - sprintf(name, "smu-i2c-%02x", *reg); - of_platform_device_create(np, name, &smu->of_dev->dev); - } + if (device_is_compatible(np, "smu-i2c-control")) { + gp = NULL; + while ((gp = of_get_next_child(np, gp)) != NULL) + if (device_is_compatible(gp, "i2c-bus")) + smu_create_i2c(gp); + } else if (device_is_compatible(np, "smu-i2c")) + smu_create_i2c(np); if (device_is_compatible(np, "smu-sensors")) - of_platform_device_create(np, "smu-sensors", &smu->of_dev->dev); + of_platform_device_create(np, "smu-sensors", + &smu->of_dev->dev); } } @@ -712,13 +730,13 @@ static void smu_i2c_complete_command(str static void smu_i2c_retry(unsigned long data) { - struct smu_i2c_cmd *cmd = (struct smu_i2c_cmd *)data; + struct smu_i2c_cmd *cmd = smu->cmd_i2c_cur; DPRINTK("SMU: i2c failure, requeuing...\n"); /* requeue command simply by resetting reply_len */ cmd->pdata[0] = 0xff; - cmd->scmd.reply_len = 0x10; + cmd->scmd.reply_len = sizeof(cmd->pdata); smu_queue_cmd(&cmd->scmd); } @@ -747,10 +765,8 @@ static void smu_i2c_low_completion(struc */ if (fail && --cmd->retries > 0) { DPRINTK("SMU: i2c failure, starting timer...\n"); - smu->i2c_timer.function = smu_i2c_retry; - smu->i2c_timer.data = (unsigned long)cmd; - smu->i2c_timer.expires = jiffies + msecs_to_jiffies(5); - add_timer(&smu->i2c_timer); + BUG_ON(cmd != smu->cmd_i2c_cur); + mod_timer(&smu->i2c_timer, jiffies + msecs_to_jiffies(5)); return; } @@ -764,7 +780,7 @@ static void smu_i2c_low_completion(struc /* Ok, initial command complete, now poll status */ scmd->reply_buf = cmd->pdata; - scmd->reply_len = 0x10; + scmd->reply_len = sizeof(cmd->pdata); scmd->data_buf = cmd->pdata; scmd->data_len = 1; cmd->pdata[0] = 0; @@ -786,7 +802,7 @@ int smu_queue_i2c(struct smu_i2c_cmd *cm cmd->scmd.done = smu_i2c_low_completion; cmd->scmd.misc = cmd; cmd->scmd.reply_buf = cmd->pdata; - cmd->scmd.reply_len = 0x10; + cmd->scmd.reply_len = sizeof(cmd->pdata); cmd->scmd.data_buf = (u8 *)(char *)&cmd->info; cmd->scmd.status = 1; cmd->stage = 0; Index: linux-work/include/asm-powerpc/smu.h =================================================================== --- linux-work.orig/include/asm-powerpc/smu.h 2006-01-06 19:39:44.000000000 +1100 +++ linux-work/include/asm-powerpc/smu.h 2006-01-07 11:25:37.000000000 +1100 @@ -358,6 +358,9 @@ extern unsigned long smu_cmdbuf_abs; * Kenrel asynchronous i2c interface */ +#define SMU_I2C_READ_MAX 0x1d +#define SMU_I2C_WRITE_MAX 0x15 + /* SMU i2c header, exactly matches i2c header on wire */ struct smu_i2c_param { @@ -368,12 +371,9 @@ struct smu_i2c_param u8 subaddr[3]; /* subaddress */ u8 caddr; /* combined address, filled by SMU driver */ u8 datalen; /* length of transfer */ - u8 data[7]; /* data */ + u8 data[SMU_I2C_READ_MAX]; /* data */ }; -#define SMU_I2C_READ_MAX 0x0d -#define SMU_I2C_WRITE_MAX 0x05 - struct smu_i2c_cmd { /* public */ @@ -387,7 +387,7 @@ struct smu_i2c_cmd int read; int stage; int retries; - u8 pdata[0x10]; + u8 pdata[32]; struct list_head link; }; @@ -519,7 +519,7 @@ struct smu_sdbp_cpupiddata { * if not found. The data format is described below */ extern struct smu_sdbp_header *smu_get_sdb_partition(int id, - unsigned int *size); + unsigned int *size); #endif /* __KERNEL__ */ From benh at kernel.crashing.org Sat Jan 7 11:35:26 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sat, 07 Jan 2006 11:35:26 +1100 Subject: [PATCH] 2/5 powerpc: Rework PowerMac i2c part 2 Message-ID: <1136594126.4840.167.camel@localhost.localdomain> This is the continuation of the previous patch. This one removes the old PowerMac i2c drivers (i2c-keywest and i2c-pmac-smu) and replaces them both with a single stub driver that uses the new PowerMac low i2c layer. Now that i2c-keywest is gone, the low-i2c code is extended to support interrupt driver transfers. All i2c busses now appear as platform devices. Compatibility with existing drivers should be maintained as the i2c bus names have been kept identical, except for the SMU bus but in that later case, all users has been fixed. With that patch added, matching a device node to an i2c_adapter becomes trivial. Signed-off-by: Benjamin Herrenschmidt Index: linux-work/drivers/i2c/busses/Kconfig =================================================================== --- linux-work.orig/drivers/i2c/busses/Kconfig 2006-01-06 19:42:48.000000000 +1100 +++ linux-work/drivers/i2c/busses/Kconfig 2006-01-06 19:43:28.000000000 +1100 @@ -236,27 +236,17 @@ config I2C_IXP2000 This support is also available as a module. If so, the module will be called i2c-ixp2000. -config I2C_KEYWEST - tristate "Powermac Keywest I2C interface" +config I2C_POWERMAC + tristate "Powermac I2C interface" depends on I2C && PPC_PMAC + default y help - This supports the use of the I2C interface in the combo-I/O - chip on recent Apple machines. Say Y if you have such a machine. - - This support is also available as a module. If so, the module - will be called i2c-keywest. - -config I2C_PMAC_SMU - tristate "Powermac SMU I2C interface" - depends on I2C && PMAC_SMU - help - This supports the use of the I2C interface in the SMU - chip on recent Apple machines like the iMac G5. It is used - among others by the thermal control driver for those machines. - Say Y if you have such a machine. + This exposes the various PowerMac i2c interfaces to the linux i2c + layer and to userland. It is used by various drivers on the powemac + platform, thus should generally be enabled. This support is also available as a module. If so, the module - will be called i2c-pmac-smu. + will be called i2c-powermac. config I2C_MPC tristate "MPC107/824x/85xx/52xx" Index: linux-work/drivers/i2c/busses/Makefile =================================================================== --- linux-work.orig/drivers/i2c/busses/Makefile 2006-01-06 19:42:48.000000000 +1100 +++ linux-work/drivers/i2c/busses/Makefile 2006-01-06 19:43:28.000000000 +1100 @@ -19,8 +19,7 @@ obj-$(CONFIG_I2C_ISA) += i2c-isa.o obj-$(CONFIG_I2C_ITE) += i2c-ite.o obj-$(CONFIG_I2C_IXP2000) += i2c-ixp2000.o obj-$(CONFIG_I2C_IXP4XX) += i2c-ixp4xx.o -obj-$(CONFIG_I2C_KEYWEST) += i2c-keywest.o -obj-$(CONFIG_I2C_PMAC_SMU) += i2c-pmac-smu.o +obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o Index: linux-work/drivers/i2c/busses/i2c-keywest.c =================================================================== --- linux-work.orig/drivers/i2c/busses/i2c-keywest.c 2006-01-06 19:42:48.000000000 +1100 +++ /dev/null 1970-01-01 00:00:00.000000000 +0000 @@ -1,754 +0,0 @@ -/* - i2c Support for Apple Keywest I2C Bus Controller - - Copyright (c) 2001 Benjamin Herrenschmidt - - Original work by - - Copyright (c) 2000 Philip Edelbrock - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - - Changes: - - 2001/12/13 BenH New implementation - 2001/12/15 BenH Add support for "byte" and "quick" - transfers. Add i2c_xfer routine. - 2003/09/21 BenH Rework state machine with Paulus help - 2004/01/21 BenH Merge in Greg KH changes, polled mode is back - 2004/02/05 BenH Merge 64 bits fixes from the g5 ppc64 tree - - My understanding of the various modes supported by keywest are: - - - Dumb mode : not implemented, probably direct tweaking of lines - - Standard mode : simple i2c transaction of type - S Addr R/W A Data A Data ... T - - Standard sub mode : combined 8 bit subaddr write with data read - S Addr R/W A SubAddr A Data A Data ... T - - Combined mode : Subaddress and Data sequences appended with no stop - S Addr R/W A SubAddr S Addr R/W A Data A Data ... T - - Currently, this driver uses only Standard mode for i2c xfer, and - smbus byte & quick transfers ; and uses StandardSub mode for - other smbus transfers instead of combined as we need that for the - sound driver to be happy -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "i2c-keywest.h" - -#undef POLLED_MODE - -/* Some debug macros */ -#define WRONG_STATE(name) do {\ - pr_debug("KW: wrong state. Got %s, state: %s (isr: %02x)\n", \ - name, __kw_state_names[iface->state], isr); \ - } while(0) - -#ifdef DEBUG -static const char *__kw_state_names[] = { - "state_idle", - "state_addr", - "state_read", - "state_write", - "state_stop", - "state_dead" -}; -#endif /* DEBUG */ - -MODULE_AUTHOR("Benjamin Herrenschmidt "); -MODULE_DESCRIPTION("I2C driver for Apple's Keywest"); -MODULE_LICENSE("GPL"); - -#ifdef POLLED_MODE -/* Don't schedule, the g5 fan controller is too - * timing sensitive - */ -static u8 -wait_interrupt(struct keywest_iface* iface) -{ - int i; - u8 isr; - - for (i = 0; i < 200000; i++) { - isr = read_reg(reg_isr) & KW_I2C_IRQ_MASK; - if (isr != 0) - return isr; - udelay(10); - } - return isr; -} -#endif /* POLLED_MODE */ - -static void -do_stop(struct keywest_iface* iface, int result) -{ - write_reg(reg_control, KW_I2C_CTL_STOP); - iface->state = state_stop; - iface->result = result; -} - -/* Main state machine for standard & standard sub mode */ -static void -handle_interrupt(struct keywest_iface *iface, u8 isr) -{ - int ack; - - if (isr == 0) { - if (iface->state != state_stop) { - pr_debug("KW: Timeout !\n"); - do_stop(iface, -EIO); - } - if (iface->state == state_stop) { - ack = read_reg(reg_status); - if (!(ack & KW_I2C_STAT_BUSY)) { - iface->state = state_idle; - write_reg(reg_ier, 0x00); -#ifndef POLLED_MODE - complete(&iface->complete); -#endif /* POLLED_MODE */ - } - } - return; - } - - if (isr & KW_I2C_IRQ_ADDR) { - ack = read_reg(reg_status); - if (iface->state != state_addr) { - write_reg(reg_isr, KW_I2C_IRQ_ADDR); - WRONG_STATE("KW_I2C_IRQ_ADDR"); - do_stop(iface, -EIO); - return; - } - if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { - iface->state = state_stop; - iface->result = -ENODEV; - pr_debug("KW: NAK on address\n"); - } else { - /* Handle rw "quick" mode */ - if (iface->datalen == 0) { - do_stop(iface, 0); - } else if (iface->read_write == I2C_SMBUS_READ) { - iface->state = state_read; - if (iface->datalen > 1) - write_reg(reg_control, KW_I2C_CTL_AAK); - } else { - iface->state = state_write; - write_reg(reg_data, *(iface->data++)); - iface->datalen--; - } - } - write_reg(reg_isr, KW_I2C_IRQ_ADDR); - } - - if (isr & KW_I2C_IRQ_DATA) { - if (iface->state == state_read) { - *(iface->data++) = read_reg(reg_data); - write_reg(reg_isr, KW_I2C_IRQ_DATA); - iface->datalen--; - if (iface->datalen == 0) - iface->state = state_stop; - else if (iface->datalen == 1) - write_reg(reg_control, 0); - } else if (iface->state == state_write) { - /* Check ack status */ - ack = read_reg(reg_status); - if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { - pr_debug("KW: nack on data write (%x): %x\n", - iface->data[-1], ack); - do_stop(iface, -EIO); - } else if (iface->datalen) { - write_reg(reg_data, *(iface->data++)); - iface->datalen--; - } else { - write_reg(reg_control, KW_I2C_CTL_STOP); - iface->state = state_stop; - iface->result = 0; - } - write_reg(reg_isr, KW_I2C_IRQ_DATA); - } else { - write_reg(reg_isr, KW_I2C_IRQ_DATA); - WRONG_STATE("KW_I2C_IRQ_DATA"); - if (iface->state != state_stop) - do_stop(iface, -EIO); - } - } - - if (isr & KW_I2C_IRQ_STOP) { - write_reg(reg_isr, KW_I2C_IRQ_STOP); - if (iface->state != state_stop) { - WRONG_STATE("KW_I2C_IRQ_STOP"); - iface->result = -EIO; - } - iface->state = state_idle; - write_reg(reg_ier, 0x00); -#ifndef POLLED_MODE - complete(&iface->complete); -#endif /* POLLED_MODE */ - } - - if (isr & KW_I2C_IRQ_START) - write_reg(reg_isr, KW_I2C_IRQ_START); -} - -#ifndef POLLED_MODE - -/* Interrupt handler */ -static irqreturn_t -keywest_irq(int irq, void *dev_id, struct pt_regs *regs) -{ - struct keywest_iface *iface = (struct keywest_iface *)dev_id; - unsigned long flags; - - spin_lock_irqsave(&iface->lock, flags); - del_timer(&iface->timeout_timer); - handle_interrupt(iface, read_reg(reg_isr)); - if (iface->state != state_idle) { - iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; - add_timer(&iface->timeout_timer); - } - spin_unlock_irqrestore(&iface->lock, flags); - return IRQ_HANDLED; -} - -static void -keywest_timeout(unsigned long data) -{ - struct keywest_iface *iface = (struct keywest_iface *)data; - unsigned long flags; - - pr_debug("timeout !\n"); - spin_lock_irqsave(&iface->lock, flags); - handle_interrupt(iface, read_reg(reg_isr)); - if (iface->state != state_idle) { - iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; - add_timer(&iface->timeout_timer); - } - spin_unlock_irqrestore(&iface->lock, flags); -} - -#endif /* POLLED_MODE */ - -/* - * SMBUS-type transfer entrypoint - */ -static s32 -keywest_smbus_xfer( struct i2c_adapter* adap, - u16 addr, - unsigned short flags, - char read_write, - u8 command, - int size, - union i2c_smbus_data* data) -{ - struct keywest_chan* chan = i2c_get_adapdata(adap); - struct keywest_iface* iface = chan->iface; - int len; - u8* buffer; - u16 cur_word; - int rc = 0; - - if (iface->state == state_dead) - return -ENXIO; - - /* Prepare datas & select mode */ - iface->cur_mode &= ~KW_I2C_MODE_MODE_MASK; - switch (size) { - case I2C_SMBUS_QUICK: - len = 0; - buffer = NULL; - iface->cur_mode |= KW_I2C_MODE_STANDARD; - break; - case I2C_SMBUS_BYTE: - len = 1; - buffer = &data->byte; - iface->cur_mode |= KW_I2C_MODE_STANDARD; - break; - case I2C_SMBUS_BYTE_DATA: - len = 1; - buffer = &data->byte; - iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; - break; - case I2C_SMBUS_WORD_DATA: - len = 2; - cur_word = cpu_to_le16(data->word); - buffer = (u8 *)&cur_word; - iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; - break; - case I2C_SMBUS_BLOCK_DATA: - len = data->block[0]; - buffer = &data->block[1]; - iface->cur_mode |= KW_I2C_MODE_STANDARDSUB; - break; - default: - return -1; - } - - /* Turn a standardsub read into a combined mode access */ - if (read_write == I2C_SMBUS_READ - && (iface->cur_mode & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_STANDARDSUB) { - iface->cur_mode &= ~KW_I2C_MODE_MODE_MASK; - iface->cur_mode |= KW_I2C_MODE_COMBINED; - } - - /* Original driver had this limitation */ - if (len > 32) - len = 32; - - if (pmac_low_i2c_lock(iface->node)) - return -ENXIO; - - pr_debug("chan: %d, addr: 0x%x, transfer len: %d, read: %d\n", - chan->chan_no, addr, len, read_write == I2C_SMBUS_READ); - - iface->data = buffer; - iface->datalen = len; - iface->state = state_addr; - iface->result = 0; - iface->read_write = read_write; - - /* Setup channel & clear pending irqs */ - write_reg(reg_isr, read_reg(reg_isr)); - write_reg(reg_mode, iface->cur_mode | (chan->chan_no << 4)); - write_reg(reg_status, 0); - - /* Set up address and r/w bit */ - write_reg(reg_addr, - (addr << 1) | ((read_write == I2C_SMBUS_READ) ? 0x01 : 0x00)); - - /* Set up the sub address */ - if ((iface->cur_mode & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_STANDARDSUB - || (iface->cur_mode & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_COMBINED) - write_reg(reg_subaddr, command); - -#ifndef POLLED_MODE - /* Arm timeout */ - iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; - add_timer(&iface->timeout_timer); -#endif - - /* Start sending address & enable interrupt*/ - write_reg(reg_control, KW_I2C_CTL_XADDR); - write_reg(reg_ier, KW_I2C_IRQ_MASK); - -#ifdef POLLED_MODE - pr_debug("using polled mode...\n"); - /* State machine, to turn into an interrupt handler */ - while(iface->state != state_idle) { - unsigned long flags; - - u8 isr = wait_interrupt(iface); - spin_lock_irqsave(&iface->lock, flags); - handle_interrupt(iface, isr); - spin_unlock_irqrestore(&iface->lock, flags); - } -#else /* POLLED_MODE */ - pr_debug("using interrupt mode...\n"); - wait_for_completion(&iface->complete); -#endif /* POLLED_MODE */ - - rc = iface->result; - pr_debug("transfer done, result: %d\n", rc); - - if (rc == 0 && size == I2C_SMBUS_WORD_DATA && read_write == I2C_SMBUS_READ) - data->word = le16_to_cpu(cur_word); - - /* Release sem */ - pmac_low_i2c_unlock(iface->node); - - return rc; -} - -/* - * Generic i2c master transfer entrypoint - */ -static int -keywest_xfer( struct i2c_adapter *adap, - struct i2c_msg *msgs, - int num) -{ - struct keywest_chan* chan = i2c_get_adapdata(adap); - struct keywest_iface* iface = chan->iface; - struct i2c_msg *pmsg; - int i, completed; - int rc = 0; - - if (iface->state == state_dead) - return -ENXIO; - - if (pmac_low_i2c_lock(iface->node)) - return -ENXIO; - - /* Set adapter to standard mode */ - iface->cur_mode &= ~KW_I2C_MODE_MODE_MASK; - iface->cur_mode |= KW_I2C_MODE_STANDARD; - - completed = 0; - for (i = 0; rc >= 0 && i < num;) { - u8 addr; - - pmsg = &msgs[i++]; - addr = pmsg->addr; - if (pmsg->flags & I2C_M_TEN) { - printk(KERN_ERR "i2c-keywest: 10 bits addr not supported !\n"); - rc = -EINVAL; - break; - } - pr_debug("xfer: chan: %d, doing %s %d bytes to 0x%02x - %d of %d messages\n", - chan->chan_no, - pmsg->flags & I2C_M_RD ? "read" : "write", - pmsg->len, addr, i, num); - - /* Setup channel & clear pending irqs */ - write_reg(reg_mode, iface->cur_mode | (chan->chan_no << 4)); - write_reg(reg_isr, read_reg(reg_isr)); - write_reg(reg_status, 0); - - iface->data = pmsg->buf; - iface->datalen = pmsg->len; - iface->state = state_addr; - iface->result = 0; - if (pmsg->flags & I2C_M_RD) - iface->read_write = I2C_SMBUS_READ; - else - iface->read_write = I2C_SMBUS_WRITE; - - /* Set up address and r/w bit */ - if (pmsg->flags & I2C_M_REV_DIR_ADDR) - addr ^= 1; - write_reg(reg_addr, - (addr << 1) | - ((iface->read_write == I2C_SMBUS_READ) ? 0x01 : 0x00)); - -#ifndef POLLED_MODE - /* Arm timeout */ - iface->timeout_timer.expires = jiffies + POLL_TIMEOUT; - add_timer(&iface->timeout_timer); -#endif - - /* Start sending address & enable interrupt*/ - write_reg(reg_ier, KW_I2C_IRQ_MASK); - write_reg(reg_control, KW_I2C_CTL_XADDR); - -#ifdef POLLED_MODE - pr_debug("using polled mode...\n"); - /* State machine, to turn into an interrupt handler */ - while(iface->state != state_idle) { - u8 isr = wait_interrupt(iface); - handle_interrupt(iface, isr); - } -#else /* POLLED_MODE */ - pr_debug("using interrupt mode...\n"); - wait_for_completion(&iface->complete); -#endif /* POLLED_MODE */ - - rc = iface->result; - if (rc == 0) - completed++; - pr_debug("transfer done, result: %d\n", rc); - } - - /* Release sem */ - pmac_low_i2c_unlock(iface->node); - - return completed; -} - -static u32 -keywest_func(struct i2c_adapter * adapter) -{ - return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | - I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | - I2C_FUNC_SMBUS_BLOCK_DATA; -} - -/* For now, we only handle combined mode (smbus) */ -static struct i2c_algorithm keywest_algorithm = { - .smbus_xfer = keywest_smbus_xfer, - .master_xfer = keywest_xfer, - .functionality = keywest_func, -}; - - -static int -create_iface(struct device_node *np, struct device *dev) -{ - unsigned long steps; - unsigned bsteps, tsize, i, nchan; - struct keywest_iface* iface; - u32 *psteps, *prate, *addrp; - int rc; - - if (np->n_intrs < 1) { - printk(KERN_ERR "%s: Missing interrupt !\n", - np->full_name); - return -ENODEV; - } - addrp = (u32 *)get_property(np, "AAPL,address", NULL); - if (addrp == NULL) { - printk(KERN_ERR "%s: Can't find address !\n", - np->full_name); - return -ENODEV; - } - - if (pmac_low_i2c_lock(np)) - return -ENODEV; - - psteps = (u32 *)get_property(np, "AAPL,address-step", NULL); - steps = psteps ? (*psteps) : 0x10; - - /* Hrm... maybe we can be smarter here */ - for (bsteps = 0; (steps & 0x01) == 0; bsteps++) - steps >>= 1; - - if (np->parent->name[0] == 'u') - nchan = 2; - else - nchan = 1; - - tsize = sizeof(struct keywest_iface) + - (sizeof(struct keywest_chan) + 4) * nchan; - iface = kzalloc(tsize, GFP_KERNEL); - if (iface == NULL) { - printk(KERN_ERR "i2c-keywest: can't allocate inteface !\n"); - pmac_low_i2c_unlock(np); - return -ENOMEM; - } - spin_lock_init(&iface->lock); - init_completion(&iface->complete); - iface->node = of_node_get(np); - iface->bsteps = bsteps; - iface->chan_count = nchan; - iface->state = state_idle; - iface->irq = np->intrs[0].line; - iface->channels = (struct keywest_chan *) - (((unsigned long)(iface + 1) + 3UL) & ~3UL); - iface->base = ioremap(*addrp, 0x1000); - if (!iface->base) { - printk(KERN_ERR "i2c-keywest: can't map inteface !\n"); - kfree(iface); - pmac_low_i2c_unlock(np); - return -ENOMEM; - } - -#ifndef POLLED_MODE - init_timer(&iface->timeout_timer); - iface->timeout_timer.function = keywest_timeout; - iface->timeout_timer.data = (unsigned long)iface; -#endif - - /* Select interface rate */ - iface->cur_mode = KW_I2C_MODE_100KHZ; - prate = (u32 *)get_property(np, "AAPL,i2c-rate", NULL); - if (prate) switch(*prate) { - case 100: - iface->cur_mode = KW_I2C_MODE_100KHZ; - break; - case 50: - iface->cur_mode = KW_I2C_MODE_50KHZ; - break; - case 25: - iface->cur_mode = KW_I2C_MODE_25KHZ; - break; - default: - printk(KERN_WARNING "i2c-keywest: unknown rate %ldKhz, using 100KHz\n", - (long)*prate); - } - - /* Select standard mode by default */ - iface->cur_mode |= KW_I2C_MODE_STANDARD; - - /* Write mode */ - write_reg(reg_mode, iface->cur_mode); - - /* Switch interrupts off & clear them*/ - write_reg(reg_ier, 0x00); - write_reg(reg_isr, KW_I2C_IRQ_MASK); - -#ifndef POLLED_MODE - /* Request chip interrupt */ - rc = request_irq(iface->irq, keywest_irq, SA_INTERRUPT, "keywest i2c", iface); - if (rc) { - printk(KERN_ERR "i2c-keywest: can't get IRQ %d !\n", iface->irq); - iounmap(iface->base); - kfree(iface); - pmac_low_i2c_unlock(np); - return -ENODEV; - } -#endif /* POLLED_MODE */ - - pmac_low_i2c_unlock(np); - dev_set_drvdata(dev, iface); - - for (i=0; ichannels[i]; - - sprintf(chan->adapter.name, "%s %d", np->parent->name, i); - chan->iface = iface; - chan->chan_no = i; - chan->adapter.algo = &keywest_algorithm; - chan->adapter.algo_data = NULL; - chan->adapter.client_register = NULL; - chan->adapter.client_unregister = NULL; - i2c_set_adapdata(&chan->adapter, chan); - chan->adapter.dev.parent = dev; - - rc = i2c_add_adapter(&chan->adapter); - if (rc) { - printk("i2c-keywest.c: Adapter %s registration failed\n", - chan->adapter.name); - i2c_set_adapdata(&chan->adapter, NULL); - } - } - - printk(KERN_INFO "Found KeyWest i2c on \"%s\", %d channel%s, stepping: %d bits\n", - np->parent->name, nchan, nchan > 1 ? "s" : "", bsteps); - - return 0; -} - -static int -dispose_iface(struct device *dev) -{ - struct keywest_iface *iface = dev_get_drvdata(dev); - int i, rc; - - /* Make sure we stop all activity */ - if (pmac_low_i2c_lock(iface->node)) - return -ENODEV; - -#ifndef POLLED_MODE - spin_lock_irq(&iface->lock); - while (iface->state != state_idle) { - spin_unlock_irq(&iface->lock); - msleep(100); - spin_lock_irq(&iface->lock); - } -#endif /* POLLED_MODE */ - iface->state = state_dead; -#ifndef POLLED_MODE - spin_unlock_irq(&iface->lock); - free_irq(iface->irq, iface); -#endif /* POLLED_MODE */ - - pmac_low_i2c_unlock(iface->node); - - /* Release all channels */ - for (i=0; ichan_count; i++) { - struct keywest_chan* chan = &iface->channels[i]; - if (i2c_get_adapdata(&chan->adapter) == NULL) - continue; - rc = i2c_del_adapter(&chan->adapter); - i2c_set_adapdata(&chan->adapter, NULL); - /* We aren't that prepared to deal with this... */ - if (rc) - printk("i2c-keywest.c: i2c_del_adapter failed, that's bad !\n"); - } - iounmap(iface->base); - dev_set_drvdata(dev, NULL); - of_node_put(iface->node); - kfree(iface); - - return 0; -} - -static int -create_iface_macio(struct macio_dev* dev, const struct of_device_id *match) -{ - return create_iface(dev->ofdev.node, &dev->ofdev.dev); -} - -static int -dispose_iface_macio(struct macio_dev* dev) -{ - return dispose_iface(&dev->ofdev.dev); -} - -static int -create_iface_of_platform(struct of_device* dev, const struct of_device_id *match) -{ - return create_iface(dev->node, &dev->dev); -} - -static int -dispose_iface_of_platform(struct of_device* dev) -{ - return dispose_iface(&dev->dev); -} - -static struct of_device_id i2c_keywest_match[] = -{ - { - .type = "i2c", - .compatible = "keywest" - }, - {}, -}; - -static struct macio_driver i2c_keywest_macio_driver = -{ - .owner = THIS_MODULE, - .name = "i2c-keywest", - .match_table = i2c_keywest_match, - .probe = create_iface_macio, - .remove = dispose_iface_macio -}; - -static struct of_platform_driver i2c_keywest_of_platform_driver = -{ - .owner = THIS_MODULE, - .name = "i2c-keywest", - .match_table = i2c_keywest_match, - .probe = create_iface_of_platform, - .remove = dispose_iface_of_platform -}; - -static int __init -i2c_keywest_init(void) -{ - of_register_driver(&i2c_keywest_of_platform_driver); - macio_register_driver(&i2c_keywest_macio_driver); - - return 0; -} - -static void __exit -i2c_keywest_cleanup(void) -{ - of_unregister_driver(&i2c_keywest_of_platform_driver); - macio_unregister_driver(&i2c_keywest_macio_driver); -} - -module_init(i2c_keywest_init); -module_exit(i2c_keywest_cleanup); Index: linux-work/drivers/i2c/busses/i2c-keywest.h =================================================================== --- linux-work.orig/drivers/i2c/busses/i2c-keywest.h 2006-01-06 19:42:48.000000000 +1100 +++ /dev/null 1970-01-01 00:00:00.000000000 +0000 @@ -1,108 +0,0 @@ -#ifndef __I2C_KEYWEST_H__ -#define __I2C_KEYWEST_H__ - -/* The Tumbler audio equalizer can be really slow sometimes */ -#define POLL_TIMEOUT (2*HZ) - -/* Register indices */ -typedef enum { - reg_mode = 0, - reg_control, - reg_status, - reg_isr, - reg_ier, - reg_addr, - reg_subaddr, - reg_data -} reg_t; - - -/* Mode register */ -#define KW_I2C_MODE_100KHZ 0x00 -#define KW_I2C_MODE_50KHZ 0x01 -#define KW_I2C_MODE_25KHZ 0x02 -#define KW_I2C_MODE_DUMB 0x00 -#define KW_I2C_MODE_STANDARD 0x04 -#define KW_I2C_MODE_STANDARDSUB 0x08 -#define KW_I2C_MODE_COMBINED 0x0C -#define KW_I2C_MODE_MODE_MASK 0x0C -#define KW_I2C_MODE_CHAN_MASK 0xF0 - -/* Control register */ -#define KW_I2C_CTL_AAK 0x01 -#define KW_I2C_CTL_XADDR 0x02 -#define KW_I2C_CTL_STOP 0x04 -#define KW_I2C_CTL_START 0x08 - -/* Status register */ -#define KW_I2C_STAT_BUSY 0x01 -#define KW_I2C_STAT_LAST_AAK 0x02 -#define KW_I2C_STAT_LAST_RW 0x04 -#define KW_I2C_STAT_SDA 0x08 -#define KW_I2C_STAT_SCL 0x10 - -/* IER & ISR registers */ -#define KW_I2C_IRQ_DATA 0x01 -#define KW_I2C_IRQ_ADDR 0x02 -#define KW_I2C_IRQ_STOP 0x04 -#define KW_I2C_IRQ_START 0x08 -#define KW_I2C_IRQ_MASK 0x0F - -/* Physical interface */ -struct keywest_iface -{ - struct device_node *node; - void __iomem * base; - unsigned bsteps; - int irq; - spinlock_t lock; - struct keywest_chan *channels; - unsigned chan_count; - u8 cur_mode; - char read_write; - u8 *data; - unsigned datalen; - int state; - int result; - struct timer_list timeout_timer; - struct completion complete; -}; - -enum { - state_idle, - state_addr, - state_read, - state_write, - state_stop, - state_dead -}; - -/* Channel on an interface */ -struct keywest_chan -{ - struct i2c_adapter adapter; - struct keywest_iface* iface; - unsigned chan_no; -}; - -/* Register access */ - -static inline u8 __read_reg(struct keywest_iface *iface, reg_t reg) -{ - return in_8(iface->base - + (((unsigned)reg) << iface->bsteps)); -} - -static inline void __write_reg(struct keywest_iface *iface, reg_t reg, u8 val) -{ - out_8(iface->base - + (((unsigned)reg) << iface->bsteps), val); - (void)__read_reg(iface, reg_subaddr); -} - -#define write_reg(reg, val) __write_reg(iface, reg, val) -#define read_reg(reg) __read_reg(iface, reg) - - - -#endif /* __I2C_KEYWEST_H__ */ Index: linux-work/drivers/i2c/busses/i2c-pmac-smu.c =================================================================== --- linux-work.orig/drivers/i2c/busses/i2c-pmac-smu.c 2006-01-06 19:42:48.000000000 +1100 +++ /dev/null 1970-01-01 00:00:00.000000000 +0000 @@ -1,324 +0,0 @@ -/* - i2c Support for Apple SMU Controller - - Copyright (c) 2005 Benjamin Herrenschmidt, IBM Corp. - - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int probe; - -MODULE_AUTHOR("Benjamin Herrenschmidt "); -MODULE_DESCRIPTION("I2C driver for Apple's SMU"); -MODULE_LICENSE("GPL"); -module_param(probe, bool, 0); - - -/* Physical interface */ -struct smu_iface -{ - struct i2c_adapter adapter; - struct completion complete; - u32 busid; -}; - -static void smu_i2c_done(struct smu_i2c_cmd *cmd, void *misc) -{ - struct smu_iface *iface = misc; - complete(&iface->complete); -} - -/* - * SMBUS-type transfer entrypoint - */ -static s32 smu_smbus_xfer( struct i2c_adapter* adap, - u16 addr, - unsigned short flags, - char read_write, - u8 command, - int size, - union i2c_smbus_data* data) -{ - struct smu_iface *iface = i2c_get_adapdata(adap); - struct smu_i2c_cmd cmd; - int rc = 0; - int read = (read_write == I2C_SMBUS_READ); - - cmd.info.bus = iface->busid; - cmd.info.devaddr = (addr << 1) | (read ? 0x01 : 0x00); - - /* Prepare datas & select mode */ - switch (size) { - case I2C_SMBUS_QUICK: - cmd.info.type = SMU_I2C_TRANSFER_SIMPLE; - cmd.info.datalen = 0; - break; - case I2C_SMBUS_BYTE: - cmd.info.type = SMU_I2C_TRANSFER_SIMPLE; - cmd.info.datalen = 1; - if (!read) - cmd.info.data[0] = data->byte; - break; - case I2C_SMBUS_BYTE_DATA: - cmd.info.type = SMU_I2C_TRANSFER_STDSUB; - cmd.info.datalen = 1; - cmd.info.sublen = 1; - cmd.info.subaddr[0] = command; - cmd.info.subaddr[1] = 0; - cmd.info.subaddr[2] = 0; - if (!read) - cmd.info.data[0] = data->byte; - break; - case I2C_SMBUS_WORD_DATA: - cmd.info.type = SMU_I2C_TRANSFER_STDSUB; - cmd.info.datalen = 2; - cmd.info.sublen = 1; - cmd.info.subaddr[0] = command; - cmd.info.subaddr[1] = 0; - cmd.info.subaddr[2] = 0; - if (!read) { - cmd.info.data[0] = data->word & 0xff; - cmd.info.data[1] = (data->word >> 8) & 0xff; - } - break; - /* Note that these are broken vs. the expected smbus API where - * on reads, the lenght is actually returned from the function, - * but I think the current API makes no sense and I don't want - * any driver that I haven't verified for correctness to go - * anywhere near a pmac i2c bus anyway ... - */ - case I2C_SMBUS_BLOCK_DATA: - cmd.info.type = SMU_I2C_TRANSFER_STDSUB; - cmd.info.datalen = data->block[0] + 1; - if (cmd.info.datalen > (SMU_I2C_WRITE_MAX + 1)) - return -EINVAL; - if (!read) - memcpy(cmd.info.data, data->block, cmd.info.datalen); - cmd.info.sublen = 1; - cmd.info.subaddr[0] = command; - cmd.info.subaddr[1] = 0; - cmd.info.subaddr[2] = 0; - break; - case I2C_SMBUS_I2C_BLOCK_DATA: - cmd.info.type = SMU_I2C_TRANSFER_STDSUB; - cmd.info.datalen = data->block[0]; - if (cmd.info.datalen > 7) - return -EINVAL; - if (!read) - memcpy(cmd.info.data, &data->block[1], - cmd.info.datalen); - cmd.info.sublen = 1; - cmd.info.subaddr[0] = command; - cmd.info.subaddr[1] = 0; - cmd.info.subaddr[2] = 0; - break; - - default: - return -EINVAL; - } - - /* Turn a standardsub read into a combined mode access */ - if (read_write == I2C_SMBUS_READ && - cmd.info.type == SMU_I2C_TRANSFER_STDSUB) - cmd.info.type = SMU_I2C_TRANSFER_COMBINED; - - /* Finish filling command and submit it */ - cmd.done = smu_i2c_done; - cmd.misc = iface; - rc = smu_queue_i2c(&cmd); - if (rc < 0) - return rc; - wait_for_completion(&iface->complete); - rc = cmd.status; - - if (!read || rc < 0) - return rc; - - switch (size) { - case I2C_SMBUS_BYTE: - case I2C_SMBUS_BYTE_DATA: - data->byte = cmd.info.data[0]; - break; - case I2C_SMBUS_WORD_DATA: - data->word = ((u16)cmd.info.data[1]) << 8; - data->word |= cmd.info.data[0]; - break; - /* Note that these are broken vs. the expected smbus API where - * on reads, the lenght is actually returned from the function, - * but I think the current API makes no sense and I don't want - * any driver that I haven't verified for correctness to go - * anywhere near a pmac i2c bus anyway ... - */ - case I2C_SMBUS_BLOCK_DATA: - case I2C_SMBUS_I2C_BLOCK_DATA: - memcpy(&data->block[0], cmd.info.data, cmd.info.datalen); - break; - } - - return rc; -} - -static u32 -smu_smbus_func(struct i2c_adapter * adapter) -{ - return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | - I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | - I2C_FUNC_SMBUS_BLOCK_DATA; -} - -/* For now, we only handle combined mode (smbus) */ -static struct i2c_algorithm smu_algorithm = { - .smbus_xfer = smu_smbus_xfer, - .functionality = smu_smbus_func, -}; - -static int create_iface(struct device_node *np, struct device *dev) -{ - struct smu_iface* iface; - u32 *reg, busid; - int rc; - - reg = (u32 *)get_property(np, "reg", NULL); - if (reg == NULL) { - printk(KERN_ERR "i2c-pmac-smu: can't find bus number !\n"); - return -ENXIO; - } - busid = *reg; - - iface = kzalloc(sizeof(struct smu_iface), GFP_KERNEL); - if (iface == NULL) { - printk(KERN_ERR "i2c-pmac-smu: can't allocate inteface !\n"); - return -ENOMEM; - } - init_completion(&iface->complete); - iface->busid = busid; - - dev_set_drvdata(dev, iface); - - sprintf(iface->adapter.name, "smu-i2c-%02x", busid); - iface->adapter.algo = &smu_algorithm; - iface->adapter.algo_data = NULL; - iface->adapter.client_register = NULL; - iface->adapter.client_unregister = NULL; - i2c_set_adapdata(&iface->adapter, iface); - iface->adapter.dev.parent = dev; - - rc = i2c_add_adapter(&iface->adapter); - if (rc) { - printk(KERN_ERR "i2c-pamc-smu.c: Adapter %s registration " - "failed\n", iface->adapter.name); - i2c_set_adapdata(&iface->adapter, NULL); - } - - if (probe) { - unsigned char addr; - printk("Probe: "); - for (addr = 0x00; addr <= 0x7f; addr++) { - if (i2c_smbus_xfer(&iface->adapter,addr, - 0,0,0,I2C_SMBUS_QUICK,NULL) >= 0) - printk("%02x ", addr); - } - printk("\n"); - } - - printk(KERN_INFO "SMU i2c bus %x registered\n", busid); - - return 0; -} - -static int dispose_iface(struct device *dev) -{ - struct smu_iface *iface = dev_get_drvdata(dev); - int rc; - - rc = i2c_del_adapter(&iface->adapter); - i2c_set_adapdata(&iface->adapter, NULL); - /* We aren't that prepared to deal with this... */ - if (rc) - printk("i2c-pmac-smu.c: Failed to remove bus %s !\n", - iface->adapter.name); - dev_set_drvdata(dev, NULL); - kfree(iface); - - return 0; -} - - -static int create_iface_of_platform(struct of_device* dev, - const struct of_device_id *match) -{ - struct device_node *node = dev->node; - - if (device_is_compatible(node, "smu-i2c") || - (node->parent != NULL && - device_is_compatible(node->parent, "smu-i2c-control"))) - return create_iface(node, &dev->dev); - return -ENODEV; -} - - -static int dispose_iface_of_platform(struct of_device* dev) -{ - return dispose_iface(&dev->dev); -} - - -static struct of_device_id i2c_smu_match[] = -{ - { - .compatible = "smu-i2c", - }, - { - .compatible = "i2c-bus", - }, - {}, -}; -static struct of_platform_driver i2c_smu_of_platform_driver = -{ - .name = "i2c-smu", - .match_table = i2c_smu_match, - .probe = create_iface_of_platform, - .remove = dispose_iface_of_platform -}; - - -static int __init i2c_pmac_smu_init(void) -{ - of_register_driver(&i2c_smu_of_platform_driver); - return 0; -} - - -static void __exit i2c_pmac_smu_cleanup(void) -{ - of_unregister_driver(&i2c_smu_of_platform_driver); -} - -module_init(i2c_pmac_smu_init); -module_exit(i2c_pmac_smu_cleanup); Index: linux-work/drivers/i2c/busses/i2c-powermac.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-work/drivers/i2c/busses/i2c-powermac.c 2006-01-07 10:53:57.000000000 +1100 @@ -0,0 +1,290 @@ +/* + i2c Support for Apple SMU Controller + + Copyright (c) 2005 Benjamin Herrenschmidt, IBM Corp. + + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Benjamin Herrenschmidt "); +MODULE_DESCRIPTION("I2C driver for Apple PowerMac"); +MODULE_LICENSE("GPL"); + +/* + * SMBUS-type transfer entrypoint + */ +static s32 i2c_powermac_smbus_xfer( struct i2c_adapter* adap, + u16 addr, + unsigned short flags, + char read_write, + u8 command, + int size, + union i2c_smbus_data* data) +{ + struct pmac_i2c_bus *bus = i2c_get_adapdata(adap); + int rc = 0; + int read = (read_write == I2C_SMBUS_READ); + int addrdir = (addr << 1) | read; + u8 local[2]; + + rc = pmac_i2c_open(bus, 0); + if (rc) + return rc; + + switch (size) { + case I2C_SMBUS_QUICK: + rc = pmac_i2c_setmode(bus, pmac_i2c_mode_std); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 0, 0, NULL, 0); + break; + case I2C_SMBUS_BYTE: + rc = pmac_i2c_setmode(bus, pmac_i2c_mode_std); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 0, 0, &data->byte, 1); + break; + case I2C_SMBUS_BYTE_DATA: + rc = pmac_i2c_setmode(bus, read ? + pmac_i2c_mode_combined : + pmac_i2c_mode_stdsub); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 1, command, &data->byte, 1); + break; + case I2C_SMBUS_WORD_DATA: + rc = pmac_i2c_setmode(bus, read ? + pmac_i2c_mode_combined : + pmac_i2c_mode_stdsub); + if (rc) + goto bail; + if (!read) { + local[0] = data->word & 0xff; + local[1] = (data->word >> 8) & 0xff; + } + rc = pmac_i2c_xfer(bus, addrdir, 1, command, local, 2); + if (rc == 0 && read) { + data->word = ((u16)local[1]) << 8; + data->word |= local[0]; + } + break; + + /* Note that these are broken vs. the expected smbus API where + * on reads, the lenght is actually returned from the function, + * but I think the current API makes no sense and I don't want + * any driver that I haven't verified for correctness to go + * anywhere near a pmac i2c bus anyway ... + * + * I'm also not completely sure what kind of phases to do between + * the actual command and the data (what I am _supposed_ to do that + * is). For now, I assume writes are a single stream and reads have + * a repeat start/addr phase (but not stop in between) + */ + case I2C_SMBUS_BLOCK_DATA: + rc = pmac_i2c_setmode(bus, read ? + pmac_i2c_mode_combined : + pmac_i2c_mode_stdsub); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 1, command, data->block, + data->block[0] + 1); + + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + rc = pmac_i2c_setmode(bus, read ? + pmac_i2c_mode_combined : + pmac_i2c_mode_stdsub); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 1, command, + read ? data->block : &data->block[1], + data->block[0]); + break; + + default: + rc = -EINVAL; + } + bail: + pmac_i2c_close(bus); + return rc; +} + +/* + * Generic i2c master transfer entrypoint. This driver only support single + * messages (for "lame i2c" transfers). Anything else should use the smbus + * entry point + */ +static int i2c_powermac_master_xfer( struct i2c_adapter *adap, + struct i2c_msg *msgs, + int num) +{ + struct pmac_i2c_bus *bus = i2c_get_adapdata(adap); + int rc = 0; + int read; + int addrdir; + + if (num != 1) + return -EINVAL; + if (msgs->flags & I2C_M_TEN) + return -EINVAL; + read = (msgs->flags & I2C_M_RD) != 0; + addrdir = (msgs->addr << 1) | read; + if (msgs->flags & I2C_M_REV_DIR_ADDR) + addrdir ^= 1; + + rc = pmac_i2c_open(bus, 0); + if (rc) + return rc; + rc = pmac_i2c_setmode(bus, pmac_i2c_mode_std); + if (rc) + goto bail; + rc = pmac_i2c_xfer(bus, addrdir, 0, 0, msgs->buf, msgs->len); + bail: + pmac_i2c_close(bus); + return rc < 0 ? rc : msgs->len; +} + +static u32 i2c_powermac_func(struct i2c_adapter * adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_I2C; +} + +/* For now, we only handle smbus */ +static struct i2c_algorithm i2c_powermac_algorithm = { + .smbus_xfer = i2c_powermac_smbus_xfer, + .master_xfer = i2c_powermac_master_xfer, + .functionality = i2c_powermac_func, +}; + + +static int i2c_powermac_remove(struct device *dev) +{ + struct i2c_adapter *adapter = dev_get_drvdata(dev); + struct pmac_i2c_bus *bus = i2c_get_adapdata(adapter); + int rc; + + rc = i2c_del_adapter(adapter); + pmac_i2c_detach_adapter(bus, adapter); + i2c_set_adapdata(adapter, NULL); + /* We aren't that prepared to deal with this... */ + if (rc) + printk("i2c-powermac.c: Failed to remove bus %s !\n", + adapter->name); + dev_set_drvdata(dev, NULL); + kfree(adapter); + + return 0; +} + + +static int i2c_powermac_probe(struct device *dev) +{ + struct pmac_i2c_bus *bus = dev->platform_data; + struct device_node *parent = NULL; + struct i2c_adapter *adapter; + char name[32], *basename; + int rc; + + if (bus == NULL) + return -EINVAL; + + /* Ok, now we need to make up a name for the interface that will + * match what we used to do in the past, that is basically the + * controller's parent device node for keywest. PMU didn't have a + * naming convention and SMU has a different one + */ + switch(pmac_i2c_get_type(bus)) { + case pmac_i2c_bus_keywest: + parent = of_get_parent(pmac_i2c_get_controller(bus)); + if (parent == NULL) + return -EINVAL; + basename = parent->name; + break; + case pmac_i2c_bus_pmu: + basename = "pmu"; + break; + case pmac_i2c_bus_smu: + /* This is not what we used to do but I'm fixing drivers at + * the same time as this change + */ + basename = "smu"; + break; + default: + return -EINVAL; + } + snprintf(name, 32, "%s %d", basename, pmac_i2c_get_channel(bus)); + of_node_put(parent); + + adapter = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL); + if (adapter == NULL) { + printk(KERN_ERR "i2c-powermac: can't allocate inteface !\n"); + return -ENOMEM; + } + dev_set_drvdata(dev, adapter); + strcpy(adapter->name, name); + adapter->algo = &i2c_powermac_algorithm; + i2c_set_adapdata(adapter, bus); + adapter->dev.parent = dev; + pmac_i2c_attach_adapter(bus, adapter); + rc = i2c_add_adapter(adapter); + if (rc) { + printk(KERN_ERR "i2c-powermac: Adapter %s registration " + "failed\n", name); + i2c_set_adapdata(adapter, NULL); + pmac_i2c_detach_adapter(bus, adapter); + } + + printk(KERN_INFO "PowerMac i2c bus %s registered\n", name); + return rc; +} + + +static struct device_driver i2c_powermac_driver = { + .name = "i2c-powermac", + .bus = &platform_bus_type, + .probe = i2c_powermac_probe, + .remove = i2c_powermac_remove, +}; + +static int __init i2c_powermac_init(void) +{ + driver_register(&i2c_powermac_driver); + return 0; +} + + +static void __exit i2c_powermac_cleanup(void) +{ + driver_unregister(&i2c_powermac_driver); +} + +module_init(i2c_powermac_init); +module_exit(i2c_powermac_cleanup); Index: linux-work/arch/powerpc/platforms/powermac/low_i2c.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/low_i2c.c 2006-01-06 19:43:21.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/low_i2c.c 2006-01-07 10:53:21.000000000 +1100 @@ -39,6 +39,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -63,6 +67,9 @@ #define DBG_LOW(x...) #endif + +static int pmac_i2c_force_poll = 1; + /* * A bus structure. Each bus in the system has such a structure associated. */ @@ -80,6 +87,7 @@ struct pmac_i2c_bus struct semaphore sem; int opened; int polled; /* open mode */ + struct platform_device *platform_dev; /* ops */ int (*open)(struct pmac_i2c_bus *bus); @@ -101,6 +109,16 @@ struct pmac_i2c_host_kw void __iomem *base; /* register base address */ int bsteps; /* register stepping */ int speed; /* speed */ + int irq; + u8 *data; + unsigned len; + int state; + int rw; + int polled; + int result; + struct completion complete; + spinlock_t lock; + struct timer_list timeout_timer; }; /* Register indices */ @@ -115,6 +133,8 @@ typedef enum { reg_data } reg_t; +/* The Tumbler audio equalizer can be really slow sometimes */ +#define KW_POLL_TIMEOUT (2*HZ) /* Mode register */ #define KW_I2C_MODE_100KHZ 0x00 @@ -158,8 +178,9 @@ enum { }; #define WRONG_STATE(name) do {\ - printk(KERN_DEBUG "KW: wrong state. Got %s, state: %s (isr: %02x)\n", \ - name, __kw_state_names[state], isr); \ + printk(KERN_DEBUG "KW: wrong state. Got %s, state: %s " \ + "(isr: %02x)\n", \ + name, __kw_state_names[host->state], isr); \ } while(0) static const char *__kw_state_names[] = { @@ -171,23 +192,22 @@ static const char *__kw_state_names[] = "state_dead" }; -static inline u8 __kw_read_reg(struct pmac_i2c_bus *bus, reg_t reg) +static inline u8 __kw_read_reg(struct pmac_i2c_host_kw *host, reg_t reg) { - struct pmac_i2c_host_kw *host = bus->hostdata; return readb(host->base + (((unsigned int)reg) << host->bsteps)); } -static inline void __kw_write_reg(struct pmac_i2c_bus *bus, reg_t reg, u8 val) +static inline void __kw_write_reg(struct pmac_i2c_host_kw *host, + reg_t reg, u8 val) { - struct pmac_i2c_host_kw *host = bus->hostdata; writeb(val, host->base + (((unsigned)reg) << host->bsteps)); - (void)__kw_read_reg(bus, reg_subaddr); + (void)__kw_read_reg(host, reg_subaddr); } -#define kw_write_reg(reg, val) __kw_write_reg(bus, reg, val) -#define kw_read_reg(reg) __kw_read_reg(bus, reg) +#define kw_write_reg(reg, val) __kw_write_reg(host, reg, val) +#define kw_read_reg(reg) __kw_read_reg(host, reg) -static u8 kw_i2c_wait_interrupt(struct pmac_i2c_bus* bus) +static u8 kw_i2c_wait_interrupt(struct pmac_i2c_host_kw *host) { int i, j; u8 isr; @@ -201,8 +221,8 @@ static u8 kw_i2c_wait_interrupt(struct p * on udelay nor schedule when in polled mode ! * For now, just use a bogus loop.... */ - if (bus->polled) { - for (j = 1; j < 1000000; j++) + if (host->polled) { + for (j = 1; j < 100000; j++) mb(); } else msleep(1); @@ -210,86 +230,99 @@ static u8 kw_i2c_wait_interrupt(struct p return isr; } -static int kw_i2c_handle_interrupt(struct pmac_i2c_bus *bus, int state, int rw, - int *rc, u8 **data, int *len, u8 isr) +static void kw_i2c_handle_interrupt(struct pmac_i2c_host_kw *host, u8 isr) { u8 ack; DBG_LOW("kw_handle_interrupt(%s, isr: %x)\n", - __kw_state_names[state], isr); + __kw_state_names[host->state], isr); + + if (host->state == state_idle) { + printk(KERN_WARNING "low_i2c: Keywest got an out of state" + " interrupt, ignoring\n"); + kw_write_reg(reg_isr, isr); + return; + } if (isr == 0) { - if (state != state_stop) { + if (host->state != state_stop) { DBG_LOW("KW: Timeout !\n"); - *rc = -EIO; + host->result = -EIO; goto stop; } - if (state == state_stop) { + if (host->state == state_stop) { ack = kw_read_reg(reg_status); - if (!(ack & KW_I2C_STAT_BUSY)) { - state = state_idle; - kw_write_reg(reg_ier, 0x00); - } + if (ack & KW_I2C_STAT_BUSY) + kw_write_reg(reg_status, 0); + host->state = state_idle; + kw_write_reg(reg_ier, 0x00); + if (!host->polled) + complete(&host->complete); } - return state; + return; } if (isr & KW_I2C_IRQ_ADDR) { ack = kw_read_reg(reg_status); - if (state != state_addr) { + if (host->state != state_addr) { kw_write_reg(reg_isr, KW_I2C_IRQ_ADDR); WRONG_STATE("KW_I2C_IRQ_ADDR"); - *rc = -EIO; + host->result = -EIO; goto stop; } if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { - *rc = -ENODEV; + host->result = -ENODEV; DBG_LOW("KW: NAK on address\n"); - return state_stop; + host->state = state_stop; + return; } else { - if (rw) { - state = state_read; - if (*len > 1) + if (host->len == 0) { + kw_write_reg(reg_isr, KW_I2C_IRQ_ADDR); + goto stop; + } + if (host->rw) { + host->state = state_read; + if (host->len > 1) kw_write_reg(reg_control, KW_I2C_CTL_AAK); } else { - state = state_write; - kw_write_reg(reg_data, **data); - (*data)++; (*len)--; + host->state = state_write; + kw_write_reg(reg_data, *(host->data++)); + host->len--; } } kw_write_reg(reg_isr, KW_I2C_IRQ_ADDR); } if (isr & KW_I2C_IRQ_DATA) { - if (state == state_read) { - **data = kw_read_reg(reg_data); - (*data)++; (*len)--; + if (host->state == state_read) { + *(host->data++) = kw_read_reg(reg_data); + host->len--; kw_write_reg(reg_isr, KW_I2C_IRQ_DATA); - if ((*len) == 0) - state = state_stop; - else if ((*len) == 1) + if (host->len == 0) + host->state = state_stop; + else if (host->len == 1) kw_write_reg(reg_control, 0); - } else if (state == state_write) { + } else if (host->state == state_write) { ack = kw_read_reg(reg_status); if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { DBG_LOW("KW: nack on data write\n"); - *rc = -EIO; + host->result = -EIO; goto stop; - } else if (*len) { - kw_write_reg(reg_data, **data); - (*data)++; (*len)--; + } else if (host->len) { + kw_write_reg(reg_data, *(host->data++)); + host->len--; } else { kw_write_reg(reg_control, KW_I2C_CTL_STOP); - state = state_stop; - *rc = 0; + host->state = state_stop; + host->result = 0; } kw_write_reg(reg_isr, KW_I2C_IRQ_DATA); } else { kw_write_reg(reg_isr, KW_I2C_IRQ_DATA); WRONG_STATE("KW_I2C_IRQ_DATA"); - if (state != state_stop) { - *rc = -EIO; + if (host->state != state_stop) { + host->result = -EIO; goto stop; } } @@ -297,21 +330,54 @@ static int kw_i2c_handle_interrupt(struc if (isr & KW_I2C_IRQ_STOP) { kw_write_reg(reg_isr, KW_I2C_IRQ_STOP); - if (state != state_stop) { + if (host->state != state_stop) { WRONG_STATE("KW_I2C_IRQ_STOP"); - *rc = -EIO; + host->result = -EIO; } - return state_idle; + host->state = state_idle; + if (!host->polled) + complete(&host->complete); } if (isr & KW_I2C_IRQ_START) kw_write_reg(reg_isr, KW_I2C_IRQ_START); - return state; - + return; stop: kw_write_reg(reg_control, KW_I2C_CTL_STOP); - return state_stop; + host->state = state_stop; + return; +} + +/* Interrupt handler */ +static irqreturn_t kw_i2c_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct pmac_i2c_host_kw *host = dev_id; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + del_timer(&host->timeout_timer); + kw_i2c_handle_interrupt(host, kw_read_reg(reg_isr)); + if (host->state != state_idle) { + host->timeout_timer.expires = jiffies + KW_POLL_TIMEOUT; + add_timer(&host->timeout_timer); + } + spin_unlock_irqrestore(&host->lock, flags); + return IRQ_HANDLED; +} + +static void kw_i2c_timeout(unsigned long data) +{ + struct pmac_i2c_host_kw *host = (struct pmac_i2c_host_kw *)data; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + kw_i2c_handle_interrupt(host, kw_read_reg(reg_isr)); + if (host->state != state_idle) { + host->timeout_timer.expires = jiffies + KW_POLL_TIMEOUT; + add_timer(&host->timeout_timer); + } + spin_unlock_irqrestore(&host->lock, flags); } static int kw_i2c_open(struct pmac_i2c_bus *bus) @@ -332,8 +398,7 @@ static int kw_i2c_xfer(struct pmac_i2c_b { struct pmac_i2c_host_kw *host = bus->hostdata; u8 mode_reg = host->speed; - int state = state_addr; - int rc = 0; + int use_irq = host->irq != NO_IRQ && !bus->polled; /* Setup mode & subaddress if any */ switch(bus->mode) { @@ -371,18 +436,50 @@ static int kw_i2c_xfer(struct pmac_i2c_b || (mode_reg & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_COMBINED) kw_write_reg(reg_subaddr, subaddr); - /* Start sending address & disable interrupt*/ - kw_write_reg(reg_ier, 0 /*KW_I2C_IRQ_MASK*/); + /* Prepare for async operations */ + host->data = data; + host->len = len; + host->state = state_addr; + host->result = 0; + host->rw = (addrdir & 1); + host->polled = bus->polled; + + /* Enable interrupt if not using polled mode and interrupt is + * available + */ + if (use_irq) { + /* Clear completion */ + INIT_COMPLETION(host->complete); + /* Ack stale interrupts */ + kw_write_reg(reg_isr, kw_read_reg(reg_isr)); + /* Arm timeout */ + host->timeout_timer.expires = jiffies + KW_POLL_TIMEOUT; + add_timer(&host->timeout_timer); + /* Enable emission */ + kw_write_reg(reg_ier, KW_I2C_IRQ_MASK); + } + + /* Start sending address */ kw_write_reg(reg_control, KW_I2C_CTL_XADDR); - /* State machine, to turn into an interrupt handler in the future */ - while(state != state_idle) { - u8 isr = kw_i2c_wait_interrupt(bus); - state = kw_i2c_handle_interrupt(bus, state, addrdir & 1, &rc, - &data, &len, isr); + /* Wait for completion */ + if (use_irq) + wait_for_completion(&host->complete); + else { + while(host->state != state_idle) { + unsigned long flags; + + u8 isr = kw_i2c_wait_interrupt(host); + spin_lock_irqsave(&host->lock, flags); + kw_i2c_handle_interrupt(host, isr); + spin_unlock_irqrestore(&host->lock, flags); + } } - return rc; + /* Disable emission */ + kw_write_reg(reg_ier, 0); + + return host->result; } static struct pmac_i2c_host_kw *__init kw_i2c_host_init(struct device_node *np) @@ -409,6 +506,12 @@ static struct pmac_i2c_host_kw *__init k return NULL; } init_MUTEX(&host->mutex); + init_completion(&host->complete); + spin_lock_init(&host->lock); + init_timer(&host->timeout_timer); + host->timeout_timer.function = kw_i2c_timeout; + host->timeout_timer.data = (unsigned long)host; + psteps = (u32 *)get_property(np, "AAPL,address-step", NULL); steps = psteps ? (*psteps) : 0x10; for (host->bsteps = 0; (steps & 0x01) == 0; host->bsteps++) @@ -427,9 +530,28 @@ static struct pmac_i2c_host_kw *__init k host->speed = KW_I2C_MODE_25KHZ; break; } + if (np->n_intrs > 0) + host->irq = np->intrs[0].line; + else + host->irq = NO_IRQ; - printk(KERN_INFO "KeyWest i2c @0x%08x %s\n", *addrp, np->full_name); host->base = ioremap((*addrp), 0x1000); + if (host->base == NULL) { + printk(KERN_ERR "low_i2c: Can't map registers for %s\n", + np->full_name); + kfree(host); + return NULL; + } + + /* Make sure IRA is disabled */ + kw_write_reg(reg_ier, 0); + + /* Request chip interrupt */ + if (request_irq(host->irq, kw_i2c_irq, SA_SHIRQ, "keywest i2c", host)) + host->irq = NO_IRQ; + + printk(KERN_INFO "KeyWest i2c @0x%08x irq %d %s\n", + *addrp, host->irq, np->full_name); return host; } @@ -591,7 +713,7 @@ static int pmu_i2c_xfer(struct pmac_i2c_ req->nbytes = sizeof(struct pmu_i2c_hdr) + 1; req->done = pmu_i2c_complete; req->arg = ∁ - if (!read) { + if (!read && len) { memcpy(hdr->data, data, len); req->nbytes += len; } @@ -637,7 +759,8 @@ static int pmu_i2c_xfer(struct pmac_i2c_ " bytes, expected %d !\n", rlen, len); return -EIO; } - memcpy(data, &req->reply[1], len); + if (len) + memcpy(data, &req->reply[1], len); return 0; } } @@ -713,6 +836,10 @@ static int smu_i2c_xfer(struct pmac_i2c_ int read = addrdir & 1; int rc = 0; + if ((read && len > SMU_I2C_READ_MAX) || + ((!read) && len > SMU_I2C_WRITE_MAX)) + return -EINVAL; + memset(cmd, 0, sizeof(struct smu_i2c_cmd)); cmd->info.bus = bus->channel; cmd->info.devaddr = addrdir; @@ -740,7 +867,7 @@ static int smu_i2c_xfer(struct pmac_i2c_ default: return -EINVAL; } - if (!read) + if (!read && len) memcpy(cmd->info.data, data, len); init_completion(&comp); @@ -752,7 +879,7 @@ static int smu_i2c_xfer(struct pmac_i2c_ wait_for_completion(&comp); rc = cmd->status; - if (read) + if (read && len) memcpy(data, cmd->info.data, len); return rc < 0 ? rc : 0; } @@ -767,7 +894,7 @@ static void __init smu_i2c_probe(void) if (!smu_present()) return; - controller = of_find_node_by_name(NULL, "smu_i2c_control"); + controller = of_find_node_by_name(NULL, "smu-i2c-control"); if (controller == NULL) controller = of_find_node_by_name(NULL, "smu"); if (controller == NULL) @@ -884,6 +1011,13 @@ int pmac_i2c_get_flags(struct pmac_i2c_b } EXPORT_SYMBOL_GPL(pmac_i2c_get_flags); +int pmac_i2c_get_channel(struct pmac_i2c_bus *bus) +{ + return bus->channel; +} +EXPORT_SYMBOL_GPL(pmac_i2c_get_channel); + + void pmac_i2c_attach_adapter(struct pmac_i2c_bus *bus, struct i2c_adapter *adapter) { @@ -906,6 +1040,17 @@ struct i2c_adapter *pmac_i2c_get_adapter } EXPORT_SYMBOL_GPL(pmac_i2c_get_adapter); +struct pmac_i2c_bus *pmac_i2c_adapter_to_bus(struct i2c_adapter *adapter) +{ + struct pmac_i2c_bus *bus; + + list_for_each_entry(bus, &pmac_i2c_busses, link) + if (bus->adapter == adapter) + return bus; + return NULL; +} +EXPORT_SYMBOL_GPL(pmac_i2c_adapter_to_bus); + extern int pmac_i2c_match_adapter(struct device_node *dev, struct i2c_adapter *adapter) { @@ -956,7 +1101,7 @@ int pmac_i2c_open(struct pmac_i2c_bus *b int rc; down(&bus->sem); - bus->polled = polled; + bus->polled = polled || pmac_i2c_force_poll; bus->opened = 1; bus->mode = pmac_i2c_mode_std; if (bus->open && (rc = bus->open(bus)) != 0) { @@ -1034,14 +1179,43 @@ int __init pmac_i2c_init(void) kw_i2c_probe(); #ifdef CONFIG_ADB_PMU + /* Probe PMU i2c busses */ pmu_i2c_probe(); #endif #ifdef CONFIG_PMAC_SMU + /* Probe SMU i2c busses */ smu_i2c_probe(); #endif - return 0; } arch_initcall(pmac_i2c_init); +/* Since pmac_i2c_init can be called too early for the platform device + * registration, we need to do it at a later time. In our case, subsys + * happens to fit well, though I agree it's a bit of a hack... + */ +static int __init pmac_i2c_create_platform_devices(void) +{ + struct pmac_i2c_bus *bus; + int i = 0; + + /* In the case where we are initialized from smp_init(), we must + * not use the timer (and thus the irq). It's safe from now on + * though + */ + pmac_i2c_force_poll = 0; + + /* Create platform devices */ + list_for_each_entry(bus, &pmac_i2c_busses, link) { + bus->platform_dev = + platform_device_alloc("i2c-powermac", i++); + if (bus->platform_dev == NULL) + return -ENOMEM; + bus->platform_dev->dev.platform_data = bus; + platform_device_add(bus->platform_dev); + } + + return 0; +} +subsys_initcall(pmac_i2c_create_platform_devices); Index: linux-work/include/asm-powerpc/pmac_low_i2c.h =================================================================== --- linux-work.orig/include/asm-powerpc/pmac_low_i2c.h 2006-01-06 19:42:48.000000000 +1100 +++ linux-work/include/asm-powerpc/pmac_low_i2c.h 2006-01-07 10:53:21.000000000 +1100 @@ -70,6 +70,7 @@ extern struct device_node *pmac_i2c_get_ extern struct device_node *pmac_i2c_get_bus_node(struct pmac_i2c_bus *bus); extern int pmac_i2c_get_type(struct pmac_i2c_bus *bus); extern int pmac_i2c_get_flags(struct pmac_i2c_bus *bus); +extern int pmac_i2c_get_channel(struct pmac_i2c_bus *bus); /* i2c layer adapter attach/detach */ extern void pmac_i2c_attach_adapter(struct pmac_i2c_bus *bus, @@ -77,6 +78,7 @@ extern void pmac_i2c_attach_adapter(stru extern void pmac_i2c_detach_adapter(struct pmac_i2c_bus *bus, struct i2c_adapter *adapter); extern struct i2c_adapter *pmac_i2c_get_adapter(struct pmac_i2c_bus *bus); +extern struct pmac_i2c_bus *pmac_i2c_adapter_to_bus(struct i2c_adapter *adapter); /* March a device or bus with an i2c adapter structure, to be used by drivers * to match device-tree nodes with i2c adapters during adapter discovery Index: linux-work/drivers/macintosh/smu.c =================================================================== --- linux-work.orig/drivers/macintosh/smu.c 2006-01-06 19:42:48.000000000 +1100 +++ linux-work/drivers/macintosh/smu.c 2006-01-06 19:43:28.000000000 +1100 @@ -584,34 +584,14 @@ core_initcall(smu_late_init); * sysfs visibility */ -static void smu_create_i2c(struct device_node *np) -{ - char name[32]; - u32 *reg = (u32 *)get_property(np, "reg", NULL); - - if (reg != NULL) { - sprintf(name, "smu-i2c-%02x", *reg); - of_platform_device_create(np, name, &smu->of_dev->dev); - } -} - static void smu_expose_childs(void *unused) { - struct device_node *np, *gp; + struct device_node *np; - for (np = NULL; (np = of_get_next_child(smu->of_node, np)) != NULL;) { - if (device_is_compatible(np, "smu-i2c-control")) { - gp = NULL; - while ((gp = of_get_next_child(np, gp)) != NULL) - if (device_is_compatible(gp, "i2c-bus")) - smu_create_i2c(gp); - } else if (device_is_compatible(np, "smu-i2c")) - smu_create_i2c(np); + for (np = NULL; (np = of_get_next_child(smu->of_node, np)) != NULL;) if (device_is_compatible(np, "smu-sensors")) of_platform_device_create(np, "smu-sensors", &smu->of_dev->dev); - } - } static DECLARE_WORK(smu_expose_childs_work, smu_expose_childs, NULL); Index: linux-work/drivers/macintosh/windfarm_lm75_sensor.c =================================================================== --- linux-work.orig/drivers/macintosh/windfarm_lm75_sensor.c 2006-01-06 19:42:48.000000000 +1100 +++ linux-work/drivers/macintosh/windfarm_lm75_sensor.c 2006-01-06 19:43:28.000000000 +1100 @@ -21,6 +21,7 @@ #include #include #include +#include #include "windfarm.h" @@ -157,53 +158,21 @@ static struct wf_lm75_sensor *wf_lm75_cr static int wf_lm75_attach(struct i2c_adapter *adapter) { - u8 bus_id; - struct device_node *smu, *bus, *dev; - - /* We currently only deal with LM75's hanging off the SMU - * i2c busses. If we extend that driver to other/older - * machines, we should split this function into SMU-i2c, - * keywest-i2c, PMU-i2c, ... - */ + struct device_node *busnode, *dev; + struct pmac_i2c_bus *bus; DBG("wf_lm75: adapter %s detected\n", adapter->name); - if (strncmp(adapter->name, "smu-i2c-", 8) != 0) - return 0; - smu = of_find_node_by_type(NULL, "smu"); - if (smu == NULL) - return 0; - - /* Look for the bus in the device-tree */ - bus_id = (u8)simple_strtoul(adapter->name + 8, NULL, 16); - - DBG("wf_lm75: bus ID is %x\n", bus_id); - - /* Look for sensors subdir */ - for (bus = NULL; - (bus = of_get_next_child(smu, bus)) != NULL;) { - u32 *reg; - - if (strcmp(bus->name, "i2c")) - continue; - reg = (u32 *)get_property(bus, "reg", NULL); - if (reg == NULL) - continue; - if (bus_id == *reg) - break; - } - of_node_put(smu); - if (bus == NULL) { - printk(KERN_WARNING "windfarm: SMU i2c bus 0x%x not found" - " in device-tree !\n", bus_id); - return 0; - } + bus = pmac_i2c_adapter_to_bus(adapter); + if (bus == NULL) + return -ENODEV; + busnode = pmac_i2c_get_bus_node(bus); DBG("wf_lm75: bus found, looking for device...\n"); /* Now look for lm75(s) in there */ for (dev = NULL; - (dev = of_get_next_child(bus, dev)) != NULL;) { + (dev = of_get_next_child(busnode, dev)) != NULL;) { const char *loc = get_property(dev, "hwsensor-location", NULL); u32 *reg = (u32 *)get_property(dev, "reg", NULL); @@ -217,9 +186,6 @@ static int wf_lm75_attach(struct i2c_ada else if (device_is_compatible(dev, "ds1775")) wf_lm75_create(adapter, *reg, 1, loc); } - - of_node_put(bus); - return 0; } Index: linux-work/drivers/macintosh/Kconfig =================================================================== --- linux-work.orig/drivers/macintosh/Kconfig 2006-01-06 19:42:48.000000000 +1100 +++ linux-work/drivers/macintosh/Kconfig 2006-01-07 10:53:19.000000000 +1100 @@ -149,14 +149,14 @@ config MAC_EMUMOUSEBTN config THERM_WINDTUNNEL tristate "Support for thermal management on Windtunnel G4s" - depends on I2C && I2C_KEYWEST && PPC_PMAC && !PPC_PMAC64 + depends on I2C && I2C_POWERMAC && PPC_PMAC && !PPC_PMAC64 help This driver provides some thermostat and fan control for the desktop G4 "Windtunnel" config THERM_ADT746X tristate "Support for thermal mgmnt on laptops with ADT 746x chipset" - depends on I2C && I2C_KEYWEST && PPC_PMAC && !PPC_PMAC64 + depends on I2C && I2C_POWERMAC && PPC_PMAC && !PPC_PMAC64 help This driver provides some thermostat and fan control for the iBook G4, and the ATI based aluminium PowerBooks, allowing slighlty @@ -164,7 +164,7 @@ config THERM_ADT746X config THERM_PM72 tristate "Support for thermal management on PowerMac G5" - depends on I2C && I2C_KEYWEST && PPC_PMAC64 + depends on I2C && I2C_POWERMAC && PPC_PMAC64 help This driver provides thermostat and fan control for the desktop G5 machines. @@ -175,14 +175,14 @@ config WINDFARM config WINDFARM_PM81 tristate "Support for thermal management on iMac G5" depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU - select I2C_PMAC_SMU + select I2C_POWERMAC help This driver provides thermal control for the iMacG5 config WINDFARM_PM91 tristate "Support for thermal management on PowerMac9,1" depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU - select I2C_PMAC_SMU + select I2C_POWERMAC help This driver provides thermal control for the PowerMac9,1 which is the recent (SMU based) single CPU desktop G5 Index: linux-work/arch/powerpc/platforms/powermac/setup.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/setup.c 2006-01-06 19:42:48.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/setup.c 2006-01-06 19:43:28.000000000 +1100 @@ -656,7 +656,7 @@ static int pmac_check_legacy_ioport(unsi static int __init pmac_declare_of_platform_devices(void) { - struct device_node *np, *npp; + struct device_node *np; np = of_find_node_by_name(NULL, "valkyrie"); if (np) @@ -664,22 +664,6 @@ static int __init pmac_declare_of_platfo np = of_find_node_by_name(NULL, "platinum"); if (np) of_platform_device_create(np, "platinum", NULL); - npp = of_find_node_by_name(NULL, "uni-n"); - if (npp == NULL) - npp = of_find_node_by_name(NULL, "u3"); - if (npp == NULL) - npp = of_find_node_by_name(NULL, "u4"); - if (npp) { - for (np = NULL; (np = of_get_next_child(npp, np)) != NULL;) { - if (strncmp(np->name, "i2c", 3) == 0) { - of_platform_device_create(np, "uni-n-i2c", - NULL); - of_node_put(np); - break; - } - } - of_node_put(npp); - } np = of_find_node_by_type(NULL, "smu"); if (np) { of_platform_device_create(np, "smu", NULL); From benh at kernel.crashing.org Sat Jan 7 11:41:02 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sat, 07 Jan 2006 11:41:02 +1100 Subject: [PATCH] 3/5 powerpc: Add platform functions interpreter Message-ID: <1136594462.4840.174.camel@localhost.localdomain> This is the platform function interpreter itself along with the backends for UniN/U3/U4, mac-io, GPIOs and i2c. It adds the ability to execute those do-platform-* scripts in the device-tree (at least for most devices for which a backend is provided). This should replace the clock spreading hacks properly. It might also have an impact on all sort of machines since some of the scripts marked "at init" will now be executed on boot (or some other on sleep/wakeup), those will possibly do things that the kernel didn't do at all, like setting some values into some i2c devices (changing thermal sensor calibration or conversion rate) etc... Thus regression testing is MUCH welcome. Also loook for errors in dmesg. That's also why I've left rather verbose debugging enabled in this version of the patch. (I do expect some Windtunnel G4s to show some errors as they have an i2c clock chip on the PMU bus that uses some primitives that the i2c backend doesn't implement yet. I really need users that have one of those machine to come back to me so we can get that done right, though the errors themselves should be harmless, I suspect the machine might not run at full speed). Signed-off-by: Benjamin Herrenschmidt Index: linux-work/include/asm-powerpc/pmac_pfunc.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-work/include/asm-powerpc/pmac_pfunc.h 2006-01-07 10:54:03.000000000 +1100 @@ -0,0 +1,253 @@ +#ifndef __PMAC_PFUNC_H__ +#define __PMAC_PFUNC_H__ + +#include +#include + +/* Flags in command lists */ +#define PMF_FLAGS_ON_INIT 0x80000000u +#define PMF_FLGAS_ON_TERM 0x40000000u +#define PMF_FLAGS_ON_SLEEP 0x20000000u +#define PMF_FLAGS_ON_WAKE 0x10000000u +#define PMF_FLAGS_ON_DEMAND 0x08000000u +#define PMF_FLAGS_INT_GEN 0x04000000u +#define PMF_FLAGS_HIGH_SPEED 0x02000000u +#define PMF_FLAGS_LOW_SPEED 0x01000000u +#define PMF_FLAGS_SIDE_EFFECTS 0x00800000u + +/* + * Arguments to a platform function call. + * + * NOTE: By convention, pointer arguments point to an u32 + */ +struct pmf_args { + union { + u32 v; + u32 *p; + } u[4]; + unsigned int count; +}; + +/* + * A driver capable of interpreting commands provides a handlers + * structure filled with whatever handlers are implemented by this + * driver. Non implemented handlers are left NULL. + * + * PMF_STD_ARGS are the same arguments that are passed to the parser + * and that gets passed back to the various handlers. + * + * Interpreting a given function always start with a begin() call which + * returns an instance data to be passed around subsequent calls, and + * ends with an end() call. This allows the low level driver to implement + * locking policy or per-function instance data. + * + * For interrupt capable functions, irq_enable() is called when a client + * registers, and irq_disable() is called when the last client unregisters + * Note that irq_enable & irq_disable are called within a semaphore held + * by the core, thus you should not try to register yourself to some other + * pmf interrupt during those calls. + */ + +#define PMF_STD_ARGS struct pmf_function *func, void *instdata, \ + struct pmf_args *args + +struct pmf_function; + +struct pmf_handlers { + void * (*begin)(struct pmf_function *func, struct pmf_args *args); + void (*end)(struct pmf_function *func, void *instdata); + + int (*irq_enable)(struct pmf_function *func); + int (*irq_disable)(struct pmf_function *func); + + int (*write_gpio)(PMF_STD_ARGS, u8 value, u8 mask); + int (*read_gpio)(PMF_STD_ARGS, u8 mask, int rshift, u8 xor); + + int (*write_reg32)(PMF_STD_ARGS, u32 offset, u32 value, u32 mask); + int (*read_reg32)(PMF_STD_ARGS, u32 offset); + int (*write_reg16)(PMF_STD_ARGS, u32 offset, u16 value, u16 mask); + int (*read_reg16)(PMF_STD_ARGS, u32 offset); + int (*write_reg8)(PMF_STD_ARGS, u32 offset, u8 value, u8 mask); + int (*read_reg8)(PMF_STD_ARGS, u32 offset); + + int (*delay)(PMF_STD_ARGS, u32 duration); + + int (*wait_reg32)(PMF_STD_ARGS, u32 offset, u32 value, u32 mask); + int (*wait_reg16)(PMF_STD_ARGS, u32 offset, u16 value, u16 mask); + int (*wait_reg8)(PMF_STD_ARGS, u32 offset, u8 value, u8 mask); + + int (*read_i2c)(PMF_STD_ARGS, u32 len); + int (*write_i2c)(PMF_STD_ARGS, u32 len, const u8 *data); + int (*rmw_i2c)(PMF_STD_ARGS, u32 masklen, u32 valuelen, u32 totallen, + const u8 *maskdata, const u8 *valuedata); + + int (*read_cfg)(PMF_STD_ARGS, u32 offset, u32 len); + int (*write_cfg)(PMF_STD_ARGS, u32 offset, u32 len, const u8 *data); + int (*rmw_cfg)(PMF_STD_ARGS, u32 offset, u32 masklen, u32 valuelen, + u32 totallen, const u8 *maskdata, const u8 *valuedata); + + int (*read_i2c_sub)(PMF_STD_ARGS, u8 subaddr, u32 len); + int (*write_i2c_sub)(PMF_STD_ARGS, u8 subaddr, u32 len, const u8 *data); + int (*set_i2c_mode)(PMF_STD_ARGS, int mode); + int (*rmw_i2c_sub)(PMF_STD_ARGS, u8 subaddr, u32 masklen, u32 valuelen, + u32 totallen, const u8 *maskdata, + const u8 *valuedata); + + int (*read_reg32_msrx)(PMF_STD_ARGS, u32 offset, u32 mask, u32 shift, + u32 xor); + int (*read_reg16_msrx)(PMF_STD_ARGS, u32 offset, u32 mask, u32 shift, + u32 xor); + int (*read_reg8_msrx)(PMF_STD_ARGS, u32 offset, u32 mask, u32 shift, + u32 xor); + + int (*write_reg32_slm)(PMF_STD_ARGS, u32 offset, u32 shift, u32 mask); + int (*write_reg16_slm)(PMF_STD_ARGS, u32 offset, u32 shift, u32 mask); + int (*write_reg8_slm)(PMF_STD_ARGS, u32 offset, u32 shift, u32 mask); + + int (*mask_and_compare)(PMF_STD_ARGS, u32 len, const u8 *maskdata, + const u8 *valuedata); + + struct module *owner; +}; + + +/* + * Drivers who expose platform functions register at init time, this + * causes the platform functions for that device node to be parsed in + * advance and associated with the device. The data structures are + * partially public so a driver can walk the list of platform functions + * and eventually inspect the flags + */ +struct pmf_device; + +struct pmf_function { + /* All functions for a given driver are linked */ + struct list_head link; + + /* Function node & driver data */ + struct device_node *node; + void *driver_data; + + /* For internal use by core */ + struct pmf_device *dev; + + /* The name is the "xxx" in "platform-do-xxx", this is how + * platform functions are identified by this code. Some functions + * only operate for a given target, in which case the phandle is + * here (or 0 if the filter doesn't apply) + */ + const char *name; + u32 phandle; + + /* The flags for that function. You can have several functions + * with the same name and different flag + */ + u32 flags; + + /* The actual tokenized function blob */ + const void *data; + unsigned int length; + + /* Interrupt clients */ + struct list_head irq_clients; + + /* Refcounting */ + struct kref ref; +}; + +/* + * For platform functions that are interrupts, one can register + * irq_client structures. You canNOT use the same structure twice + * as it contains a link member. Also, the callback is called with + * a spinlock held, you must not call back into any of the pmf_* functions + * from within that callback + */ +struct pmf_irq_client { + void (*handler)(void *data); + void *data; + struct module *owner; + struct list_head link; +}; + + +/* + * Register/Unregister a function-capable driver and its handlers + */ +extern int pmf_register_driver(struct device_node *np, + struct pmf_handlers *handlers, + void *driverdata); + +extern void pmf_unregister_driver(struct device_node *np); + + +/* + * Register/Unregister interrupt clients + */ +extern int pmf_register_irq_client(struct device_node *np, + const char *name, + struct pmf_irq_client *client); + +extern void pmf_unregister_irq_client(struct device_node *np, + const char *name, + struct pmf_irq_client *client); + +/* + * Called by the handlers when an irq happens + */ +extern void pmf_do_irq(struct pmf_function *func); + + +/* + * Low level call to platform functions. + * + * The phandle can filter on the target object for functions that have + * multiple targets, the flags allow you to restrict the call to a given + * combination of flags. + * + * The args array contains as many arguments as is required by the function, + * this is dependent on the function you are calling, unfortunately Apple + * mecanism provides no way to encode that so you have to get it right at + * the call site. Some functions require no args, in which case, you can + * pass NULL. + * + * You can also pass NULL to the name. This will match any function that has + * the appropriate combination of flags & phandle or you can pass 0 to the + * phandle to match any + */ +extern int pmf_do_functions(struct device_node *np, const char *name, + u32 phandle, u32 flags, struct pmf_args *args); + + + +/* + * High level call to a platform function. + * + * This one looks for the platform-xxx first so you should call it to the + * actual target if any. It will fallback to platform-do-xxx if it can't + * find one. It will also exclusively target functions that have + * the "OnDemand" flag. + */ + +extern int pmf_call_function(struct device_node *target, const char *name, + struct pmf_args *args); + + +/* + * For low latency interrupt usage, you can lookup for on-demand functions + * using the functions below + */ + +extern struct pmf_function *pmf_find_function(struct device_node *target, + const char *name); + +extern struct pmf_function * pmf_get_function(struct pmf_function *func); +extern void pmf_put_function(struct pmf_function *func); + +extern int pmf_call_one(struct pmf_function *func, struct pmf_args *args); + + +/* Suspend/resume code called by via-pmu directly for now */ +extern void pmac_pfunc_base_suspend(void); +extern void pmac_pfunc_base_resume(void); + +#endif /* __PMAC_PFUNC_H__ */ Index: linux-work/arch/powerpc/platforms/powermac/Makefile =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/Makefile 2006-01-07 10:53:21.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/Makefile 2006-01-07 10:54:03.000000000 +1100 @@ -1,7 +1,8 @@ CFLAGS_bootx_init.o += -fPIC obj-y += pic.o setup.o time.o feature.o pci.o \ - sleep.o low_i2c.o cache.o + sleep.o low_i2c.o cache.o pfunc_core.o \ + pfunc_base.o obj-$(CONFIG_PMAC_BACKLIGHT) += backlight.o obj-$(CONFIG_CPU_FREQ_PMAC) += cpufreq_32.o obj-$(CONFIG_CPU_FREQ_PMAC64) += cpufreq_64.o Index: linux-work/arch/powerpc/platforms/powermac/feature.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/feature.c 2006-01-07 10:53:21.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/feature.c 2006-01-07 10:54:03.000000000 +1100 @@ -58,12 +58,11 @@ extern int powersave_lowspeed; extern int powersave_nap; extern struct device_node *k2_skiplist[2]; - /* * We use a single global lock to protect accesses. Each driver has * to take care of its own locking */ -static DEFINE_SPINLOCK(feature_lock); +DEFINE_SPINLOCK(feature_lock); #define LOCK(flags) spin_lock_irqsave(&feature_lock, flags); #define UNLOCK(flags) spin_unlock_irqrestore(&feature_lock, flags); @@ -106,22 +105,12 @@ static const char *macio_names[] = }; +struct device_node *uninorth_node; +u32 __iomem *uninorth_base; -/* - * Uninorth reg. access. Note that Uni-N regs are big endian - */ - -#define UN_REG(r) (uninorth_base + ((r) >> 2)) -#define UN_IN(r) (in_be32(UN_REG(r))) -#define UN_OUT(r,v) (out_be32(UN_REG(r), (v))) -#define UN_BIS(r,v) (UN_OUT((r), UN_IN(r) | (v))) -#define UN_BIC(r,v) (UN_OUT((r), UN_IN(r) & ~(v))) - -static struct device_node *uninorth_node; -static u32 __iomem *uninorth_base; static u32 uninorth_rev; static int uninorth_maj; -static void __iomem *u3_ht; +static void __iomem *u3_ht_base; /* * For each motherboard family, we have a table of functions pointers @@ -1560,8 +1549,10 @@ void g5_phy_disable_cpu1(void) #ifndef CONFIG_POWER4 -static void -keylargo_shutdown(struct macio_chip *macio, int sleep_mode) + +#ifdef CONFIG_PM + +static void keylargo_shutdown(struct macio_chip *macio, int sleep_mode) { u32 temp; @@ -1614,8 +1605,7 @@ keylargo_shutdown(struct macio_chip *mac (void)MACIO_IN32(KEYLARGO_FCR0); mdelay(1); } -static void -pangea_shutdown(struct macio_chip *macio, int sleep_mode) +static void pangea_shutdown(struct macio_chip *macio, int sleep_mode) { u32 temp; @@ -1648,8 +1638,7 @@ pangea_shutdown(struct macio_chip *macio (void)MACIO_IN32(KEYLARGO_FCR0); mdelay(1); } -static void -intrepid_shutdown(struct macio_chip *macio, int sleep_mode) +static void intrepid_shutdown(struct macio_chip *macio, int sleep_mode) { u32 temp; @@ -1833,6 +1822,8 @@ core99_wake_up(void) return 0; } +#endif /* CONFIG_PM */ + static long core99_sleep_state(struct device_node *node, long param, long value) { @@ -1854,10 +1845,13 @@ core99_sleep_state(struct device_node *n if ((pmac_mb.board_flags & PMAC_MB_CAN_SLEEP) == 0) return -EPERM; +#ifdef CONFIG_PM if (value == 1) return core99_sleep(); else if (value == 0) return core99_wake_up(); + +#endif /* CONFIG_PM */ return 0; } @@ -1981,7 +1975,9 @@ static struct feature_table_entry core99 { PMAC_FTR_USB_ENABLE, core99_usb_enable }, { PMAC_FTR_1394_ENABLE, core99_firewire_enable }, { PMAC_FTR_1394_CABLE_POWER, core99_firewire_cable_power }, +#ifdef CONFIG_PM { PMAC_FTR_SLEEP_STATE, core99_sleep_state }, +#endif #ifdef CONFIG_SMP { PMAC_FTR_RESET_CPU, core99_reset_cpu }, #endif /* CONFIG_SMP */ @@ -2572,7 +2568,7 @@ static void __init probe_uninorth(void) uninorth_base = ioremap(address, 0x40000); uninorth_rev = in_be32(UN_REG(UNI_N_VERSION)); if (uninorth_maj == 3 || uninorth_maj == 4) - u3_ht = ioremap(address + U3_HT_CONFIG_BASE, 0x1000); + u3_ht_base = ioremap(address + U3_HT_CONFIG_BASE, 0x1000); printk(KERN_INFO "Found %s memory controller & host bridge" " @ 0x%08x revision: 0x%02x\n", uninorth_maj == 3 ? "U3" : @@ -2921,9 +2917,9 @@ void __init pmac_check_ht_link(void) u8 px_bus, px_devfn; struct pci_controller *px_hose; - (void)in_be32(u3_ht + U3_HT_LINK_COMMAND); - ucfg = cfg = in_be32(u3_ht + U3_HT_LINK_CONFIG); - ufreq = freq = in_be32(u3_ht + U3_HT_LINK_FREQ); + (void)in_be32(u3_ht_base + U3_HT_LINK_COMMAND); + ucfg = cfg = in_be32(u3_ht_base + U3_HT_LINK_CONFIG); + ufreq = freq = in_be32(u3_ht_base + U3_HT_LINK_FREQ); dump_HT_speeds("U3 HyperTransport", cfg, freq); pcix_node = of_find_compatible_node(NULL, "pci", "pci-x"); Index: linux-work/arch/powerpc/platforms/powermac/pfunc_base.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-work/arch/powerpc/platforms/powermac/pfunc_base.c 2006-01-07 10:54:03.000000000 +1100 @@ -0,0 +1,405 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DBG(fmt...) printk(fmt) + +static irqreturn_t macio_gpio_irq(int irq, void *data, struct pt_regs *regs) +{ + pmf_do_irq(data); + + return IRQ_HANDLED; +} + +static int macio_do_gpio_irq_enable(struct pmf_function *func) +{ + if (func->node->n_intrs < 1) + return -EINVAL; + + return request_irq(func->node->intrs[0].line, macio_gpio_irq, 0, + func->node->name, func); +} + +static int macio_do_gpio_irq_disable(struct pmf_function *func) +{ + if (func->node->n_intrs < 1) + return -EINVAL; + + free_irq(func->node->intrs[0].line, func); + return 0; +} + +static int macio_do_gpio_write(PMF_STD_ARGS, u8 value, u8 mask) +{ + u8 __iomem *addr = (u8 __iomem *)func->driver_data; + unsigned long flags; + u8 tmp; + + /* Check polarity */ + if (args && args->count && !args->u[0].v) + value = ~value; + + /* Toggle the GPIO */ + spin_lock_irqsave(&feature_lock, flags); + tmp = readb(addr); + tmp = (tmp & ~mask) | (value & mask); + DBG("Do write 0x%02x to GPIO %s (%p)\n", + tmp, func->node->full_name, addr); + writeb(tmp, addr); + spin_unlock_irqrestore(&feature_lock, flags); + + return 0; +} + +static int macio_do_gpio_read(PMF_STD_ARGS, u8 mask, int rshift, u8 xor) +{ + u8 __iomem *addr = (u8 __iomem *)func->driver_data; + u32 value; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + value = readb(addr); + *args->u[0].p = ((value & mask) >> rshift) ^ xor; + + return 0; +} + +static int macio_do_delay(PMF_STD_ARGS, u32 duration) +{ + /* assume we can sleep ! */ + msleep((duration + 999) / 1000); + return 0; +} + +static struct pmf_handlers macio_gpio_handlers = { + .irq_enable = macio_do_gpio_irq_enable, + .irq_disable = macio_do_gpio_irq_disable, + .write_gpio = macio_do_gpio_write, + .read_gpio = macio_do_gpio_read, + .delay = macio_do_delay, +}; + +static void macio_gpio_init_one(struct macio_chip *macio) +{ + struct device_node *gparent, *gp; + + /* + * Find the "gpio" parent node + */ + + for (gparent = NULL; + (gparent = of_get_next_child(macio->of_node, gparent)) != NULL;) + if (strcmp(gparent->name, "gpio") == 0) + break; + if (gparent == NULL) + return; + + DBG("Installing GPIO functions for macio %s\n", + macio->of_node->full_name); + + /* + * Ok, got one, we dont need anything special to track them down, so + * we just create them all + */ + for (gp = NULL; (gp = of_get_next_child(gparent, gp)) != NULL;) { + u32 *reg = (u32 *)get_property(gp, "reg", NULL); + unsigned long offset; + if (reg == NULL) + continue; + offset = *reg; + /* Deal with old style device-tree. We can safely hard code the + * offset for now too even if it's a bit gross ... + */ + if (offset < 0x50) + offset += 0x50; + offset += (unsigned long)macio->base; + pmf_register_driver(gp, &macio_gpio_handlers, (void *)offset); + } + + DBG("Calling initial GPIO functions for macio %s\n", + macio->of_node->full_name); + + /* And now we run all the init ones */ + for (gp = NULL; (gp = of_get_next_child(gparent, gp)) != NULL;) + pmf_do_functions(gp, NULL, 0, PMF_FLAGS_ON_INIT, NULL); + + /* Note: We do not at this point implement the "at sleep" or "at wake" + * functions. I yet to find any for GPIOs anyway + */ +} + +static int macio_do_write_reg32(PMF_STD_ARGS, u32 offset, u32 value, u32 mask) +{ + struct macio_chip *macio = func->driver_data; + unsigned long flags; + + spin_lock_irqsave(&feature_lock, flags); + MACIO_OUT32(offset, (MACIO_IN32(offset) & ~mask) | (value & mask)); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + +static int macio_do_read_reg32(PMF_STD_ARGS, u32 offset) +{ + struct macio_chip *macio = func->driver_data; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + *args->u[0].p = MACIO_IN32(offset); + return 0; +} + +static int macio_do_write_reg8(PMF_STD_ARGS, u32 offset, u8 value, u8 mask) +{ + struct macio_chip *macio = func->driver_data; + unsigned long flags; + + spin_lock_irqsave(&feature_lock, flags); + MACIO_OUT8(offset, (MACIO_IN8(offset) & ~mask) | (value & mask)); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + +static int macio_do_read_reg8(PMF_STD_ARGS, u32 offset) +{ + struct macio_chip *macio = func->driver_data; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + *((u8 *)(args->u[0].p)) = MACIO_IN8(offset); + return 0; +} + +static int macio_do_read_reg32_msrx(PMF_STD_ARGS, u32 offset, u32 mask, + u32 shift, u32 xor) +{ + struct macio_chip *macio = func->driver_data; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + *args->u[0].p = ((MACIO_IN32(offset) & mask) >> shift) ^ xor; + return 0; +} + +static int macio_do_read_reg8_msrx(PMF_STD_ARGS, u32 offset, u32 mask, + u32 shift, u32 xor) +{ + struct macio_chip *macio = func->driver_data; + + /* Check if we have room for reply */ + if (args == NULL || args->count == 0 || args->u[0].p == NULL) + return -EINVAL; + + *((u8 *)(args->u[0].p)) = ((MACIO_IN8(offset) & mask) >> shift) ^ xor; + return 0; +} + +static int macio_do_write_reg32_slm(PMF_STD_ARGS, u32 offset, u32 shift, + u32 mask) +{ + struct macio_chip *macio = func->driver_data; + unsigned long flags; + u32 tmp, val; + + /* Check args */ + if (args == NULL || args->count == 0) + return -EINVAL; + + spin_lock_irqsave(&feature_lock, flags); + tmp = MACIO_IN32(offset); + val = args->u[0].v << shift; + tmp = (tmp & ~mask) | (val & mask); + MACIO_OUT32(offset, tmp); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + +static int macio_do_write_reg8_slm(PMF_STD_ARGS, u32 offset, u32 shift, + u32 mask) +{ + struct macio_chip *macio = func->driver_data; + unsigned long flags; + u32 tmp, val; + + /* Check args */ + if (args == NULL || args->count == 0) + return -EINVAL; + + spin_lock_irqsave(&feature_lock, flags); + tmp = MACIO_IN8(offset); + val = args->u[0].v << shift; + tmp = (tmp & ~mask) | (val & mask); + MACIO_OUT8(offset, tmp); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + +static struct pmf_handlers macio_mmio_handlers = { + .write_reg32 = macio_do_write_reg32, + .read_reg32 = macio_do_read_reg32, + .write_reg8 = macio_do_write_reg8, + .read_reg32 = macio_do_read_reg8, + .read_reg32_msrx = macio_do_read_reg32_msrx, + .read_reg8_msrx = macio_do_read_reg8_msrx, + .write_reg32_slm = macio_do_write_reg32_slm, + .write_reg8_slm = macio_do_write_reg8_slm, + .delay = macio_do_delay, +}; + +static void macio_mmio_init_one(struct macio_chip *macio) +{ + DBG("Installing MMIO functions for macio %s\n", + macio->of_node->full_name); + + pmf_register_driver(macio->of_node, &macio_mmio_handlers, macio); +} + +static struct device_node *unin_hwclock; + +static int unin_do_write_reg32(PMF_STD_ARGS, u32 offset, u32 value, u32 mask) +{ + unsigned long flags; + + spin_lock_irqsave(&feature_lock, flags); + /* This is fairly bogus in darwin, but it should work for our needs + * implemeted that way: + */ + UN_OUT(offset, (UN_IN(offset) & ~mask) | (value & mask)); + spin_unlock_irqrestore(&feature_lock, flags); + return 0; +} + + +static struct pmf_handlers unin_mmio_handlers = { + .write_reg32 = unin_do_write_reg32, + .delay = macio_do_delay, +}; + +static void uninorth_install_pfunc(void) +{ + struct device_node *np; + + DBG("Installing functions for UniN %s\n", + uninorth_node->full_name); + + /* + * Install handlers for the bridge itself + */ + pmf_register_driver(uninorth_node, &unin_mmio_handlers, NULL); + pmf_do_functions(uninorth_node, NULL, 0, PMF_FLAGS_ON_INIT, NULL); + + + /* + * Install handlers for the hwclock child if any + */ + for (np = NULL; (np = of_get_next_child(uninorth_node, np)) != NULL;) + if (strcmp(np->name, "hw-clock") == 0) { + unin_hwclock = np; + break; + } + if (unin_hwclock) { + DBG("Installing functions for UniN clock %s\n", + unin_hwclock->full_name); + pmf_register_driver(unin_hwclock, &unin_mmio_handlers, NULL); + pmf_do_functions(unin_hwclock, NULL, 0, PMF_FLAGS_ON_INIT, + NULL); + } +} + +/* We export this as the SMP code might init us early */ +int __init pmac_pfunc_base_install(void) +{ + static int pfbase_inited; + int i; + + if (pfbase_inited) + return 0; + pfbase_inited = 1; + + + DBG("Installing base platform functions...\n"); + + /* + * Locate mac-io chips and install handlers + */ + for (i = 0 ; i < MAX_MACIO_CHIPS; i++) { + if (macio_chips[i].of_node) { + macio_mmio_init_one(&macio_chips[i]); + macio_gpio_init_one(&macio_chips[i]); + } + } + + /* + * Install handlers for northbridge and direct mapped hwclock + * if any. We do not implement the config space access callback + * which is only ever used for functions that we do not call in + * the current driver (enabling/disabling cells in U2, mostly used + * to restore the PCI settings, we do that differently) + */ + if (uninorth_node && uninorth_base) + uninorth_install_pfunc(); + + DBG("All base functions installed\n"); + + return 0; +} + +arch_initcall(pmac_pfunc_base_install); + +#ifdef CONFIG_PM + +/* Those can be called by pmac_feature. Ultimately, I should use a sysdev + * or a device, but for now, that's good enough until I sort out some + * ordering issues. Also, we do not bother with GPIOs, as so far I yet have + * to see a case where a GPIO function has the on-suspend or on-resume bit + */ +void pmac_pfunc_base_suspend(void) +{ + int i; + + for (i = 0 ; i < MAX_MACIO_CHIPS; i++) { + if (macio_chips[i].of_node) + pmf_do_functions(macio_chips[i].of_node, NULL, 0, + PMF_FLAGS_ON_SLEEP, NULL); + } + if (uninorth_node) + pmf_do_functions(uninorth_node, NULL, 0, + PMF_FLAGS_ON_SLEEP, NULL); + if (unin_hwclock) + pmf_do_functions(unin_hwclock, NULL, 0, + PMF_FLAGS_ON_SLEEP, NULL); +} + +void pmac_pfunc_base_resume(void) +{ + int i; + + if (unin_hwclock) + pmf_do_functions(unin_hwclock, NULL, 0, + PMF_FLAGS_ON_WAKE, NULL); + if (uninorth_node) + pmf_do_functions(uninorth_node, NULL, 0, + PMF_FLAGS_ON_WAKE, NULL); + for (i = 0 ; i < MAX_MACIO_CHIPS; i++) { + if (macio_chips[i].of_node) + pmf_do_functions(macio_chips[i].of_node, NULL, 0, + PMF_FLAGS_ON_WAKE, NULL); + } +} + +#endif /* CONFIG_PM */ Index: linux-work/arch/powerpc/platforms/powermac/pfunc_core.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-work/arch/powerpc/platforms/powermac/pfunc_core.c 2006-01-07 10:54:03.000000000 +1100 @@ -0,0 +1,989 @@ +/* + * + * FIXME: Properly make this race free with refcounting etc... + * + * FIXME: LOCKING !!! + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Debug */ +#define LOG_PARSE(fmt...) +#define LOG_ERROR(fmt...) printk(fmt) +#define LOG_BLOB(t,b,c) +#define DBG(fmt...) printk(fmt) + +/* Command numbers */ +#define PMF_CMD_LIST 0 +#define PMF_CMD_WRITE_GPIO 1 +#define PMF_CMD_READ_GPIO 2 +#define PMF_CMD_WRITE_REG32 3 +#define PMF_CMD_READ_REG32 4 +#define PMF_CMD_WRITE_REG16 5 +#define PMF_CMD_READ_REG16 6 +#define PMF_CMD_WRITE_REG8 7 +#define PMF_CMD_READ_REG8 8 +#define PMF_CMD_DELAY 9 +#define PMF_CMD_WAIT_REG32 10 +#define PMF_CMD_WAIT_REG16 11 +#define PMF_CMD_WAIT_REG8 12 +#define PMF_CMD_READ_I2C 13 +#define PMF_CMD_WRITE_I2C 14 +#define PMF_CMD_RMW_I2C 15 +#define PMF_CMD_GEN_I2C 16 +#define PMF_CMD_SHIFT_BYTES_RIGHT 17 +#define PMF_CMD_SHIFT_BYTES_LEFT 18 +#define PMF_CMD_READ_CFG 19 +#define PMF_CMD_WRITE_CFG 20 +#define PMF_CMD_RMW_CFG 21 +#define PMF_CMD_READ_I2C_SUBADDR 22 +#define PMF_CMD_WRITE_I2C_SUBADDR 23 +#define PMF_CMD_SET_I2C_MODE 24 +#define PMF_CMD_RMW_I2C_SUBADDR 25 +#define PMF_CMD_READ_REG32_MASK_SHR_XOR 26 +#define PMF_CMD_READ_REG16_MASK_SHR_XOR 27 +#define PMF_CMD_READ_REG8_MASK_SHR_XOR 28 +#define PMF_CMD_WRITE_REG32_SHL_MASK 29 +#define PMF_CMD_WRITE_REG16_SHL_MASK 30 +#define PMF_CMD_WRITE_REG8_SHL_MASK 31 +#define PMF_CMD_MASK_AND_COMPARE 32 +#define PMF_CMD_COUNT 33 + +/* This structure holds the state of the parser while walking through + * a function definition + */ +struct pmf_cmd { + const void *cmdptr; + const void *cmdend; + struct pmf_function *func; + void *instdata; + struct pmf_args *args; + int error; +}; + +#if 0 +/* Debug output */ +static void print_blob(const char *title, const void *blob, int bytes) +{ + printk("%s", title); + while(bytes--) { + printk("%02x ", *((u8 *)blob)); + blob += 1; + } + printk("\n"); +} +#endif + +/* + * Parser helpers + */ + +static u32 pmf_next32(struct pmf_cmd *cmd) +{ + u32 value; + if ((cmd->cmdend - cmd->cmdptr) < 4) { + cmd->error = 1; + return 0; + } + value = *((u32 *)cmd->cmdptr); + cmd->cmdptr += 4; + return value; +} + +static const void* pmf_next_blob(struct pmf_cmd *cmd, int count) +{ + const void *value; + if ((cmd->cmdend - cmd->cmdptr) < count) { + cmd->error = 1; + return NULL; + } + value = cmd->cmdptr; + cmd->cmdptr += count; + return value; +} + +/* + * Individual command parsers + */ + +#define PMF_PARSE_CALL(name, cmd, handlers, p...) \ + do { \ + if (cmd->error) \ + return -ENXIO; \ + if (handlers == NULL) \ + return 0; \ + if (handlers->name) \ + return handlers->name(cmd->func, cmd->instdata, \ + cmd->args, p); \ + return -1; \ + } while(0) \ + + +static int pmf_parser_write_gpio(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 value = (u8)pmf_next32(cmd); + u8 mask = (u8)pmf_next32(cmd); + + LOG_PARSE("pmf: write_gpio(value: %02x, mask: %02x)\n", value, mask); + + PMF_PARSE_CALL(write_gpio, cmd, h, value, mask); +} + +static int pmf_parser_read_gpio(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 mask = (u8)pmf_next32(cmd); + int rshift = (int)pmf_next32(cmd); + u8 xor = (u8)pmf_next32(cmd); + + LOG_PARSE("pmf: read_gpio(mask: %02x, rshift: %d, xor: %02x)\n", + mask, rshift, xor); + + PMF_PARSE_CALL(read_gpio, cmd, h, mask, rshift, xor); +} + +static int pmf_parser_write_reg32(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 value = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg32(offset: %08x, value: %08x, mask: %08x)\n", + offset, value, mask); + + PMF_PARSE_CALL(write_reg32, cmd, h, offset, value, mask); +} + +static int pmf_parser_read_reg32(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg32(offset: %08x)\n", offset); + + PMF_PARSE_CALL(read_reg32, cmd, h, offset); +} + + +static int pmf_parser_write_reg16(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u16 value = (u16)pmf_next32(cmd); + u16 mask = (u16)pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg16(offset: %08x, value: %04x, mask: %04x)\n", + offset, value, mask); + + PMF_PARSE_CALL(write_reg16, cmd, h, offset, value, mask); +} + +static int pmf_parser_read_reg16(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg16(offset: %08x)\n", offset); + + PMF_PARSE_CALL(read_reg16, cmd, h, offset); +} + + +static int pmf_parser_write_reg8(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u8 value = (u16)pmf_next32(cmd); + u8 mask = (u16)pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg8(offset: %08x, value: %02x, mask: %02x)\n", + offset, value, mask); + + PMF_PARSE_CALL(write_reg8, cmd, h, offset, value, mask); +} + +static int pmf_parser_read_reg8(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg8(offset: %08x)\n", offset); + + PMF_PARSE_CALL(read_reg8, cmd, h, offset); +} + +static int pmf_parser_delay(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 duration = pmf_next32(cmd); + + LOG_PARSE("pmf: delay(duration: %d us)\n", duration); + + PMF_PARSE_CALL(delay, cmd, h, duration); +} + +static int pmf_parser_wait_reg32(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 value = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: wait_reg32(offset: %08x, comp_value: %08x,mask: %08x)\n", + offset, value, mask); + + PMF_PARSE_CALL(wait_reg32, cmd, h, offset, value, mask); +} + +static int pmf_parser_wait_reg16(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u16 value = (u16)pmf_next32(cmd); + u16 mask = (u16)pmf_next32(cmd); + + LOG_PARSE("pmf: wait_reg16(offset: %08x, comp_value: %04x,mask: %04x)\n", + offset, value, mask); + + PMF_PARSE_CALL(wait_reg16, cmd, h, offset, value, mask); +} + +static int pmf_parser_wait_reg8(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u8 value = (u8)pmf_next32(cmd); + u8 mask = (u8)pmf_next32(cmd); + + LOG_PARSE("pmf: wait_reg8(offset: %08x, comp_value: %02x,mask: %02x)\n", + offset, value, mask); + + PMF_PARSE_CALL(wait_reg8, cmd, h, offset, value, mask); +} + +static int pmf_parser_read_i2c(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 bytes = pmf_next32(cmd); + + LOG_PARSE("pmf: read_i2c(bytes: %ud)\n", bytes); + + PMF_PARSE_CALL(read_i2c, cmd, h, bytes); +} + +static int pmf_parser_write_i2c(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 bytes = pmf_next32(cmd); + const void *blob = pmf_next_blob(cmd, bytes); + + LOG_PARSE("pmf: write_i2c(bytes: %ud) ...\n", bytes); + LOG_BLOB("pmf: data: \n", blob, bytes); + + PMF_PARSE_CALL(write_i2c, cmd, h, bytes, blob); +} + + +static int pmf_parser_rmw_i2c(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 maskbytes = pmf_next32(cmd); + u32 valuesbytes = pmf_next32(cmd); + u32 totalbytes = pmf_next32(cmd); + const void *maskblob = pmf_next_blob(cmd, maskbytes); + const void *valuesblob = pmf_next_blob(cmd, valuesbytes); + + LOG_PARSE("pmf: rmw_i2c(maskbytes: %ud, valuebytes: %ud, " + "totalbytes: %d) ...\n", + maskbytes, valuesbytes, totalbytes); + LOG_BLOB("pmf: mask data: \n", maskblob, maskbytes); + LOG_BLOB("pmf: values data: \n", valuesblob, valuesbytes); + + PMF_PARSE_CALL(rmw_i2c, cmd, h, maskbytes, valuesbytes, totalbytes, + maskblob, valuesblob); +} + +static int pmf_parser_read_cfg(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 bytes = pmf_next32(cmd); + + LOG_PARSE("pmf: read_cfg(offset: %x, bytes: %ud)\n", offset, bytes); + + PMF_PARSE_CALL(read_cfg, cmd, h, offset, bytes); +} + + +static int pmf_parser_write_cfg(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 bytes = pmf_next32(cmd); + const void *blob = pmf_next_blob(cmd, bytes); + + LOG_PARSE("pmf: write_cfg(offset: %x, bytes: %ud)\n", offset, bytes); + LOG_BLOB("pmf: data: \n", blob, bytes); + + PMF_PARSE_CALL(write_cfg, cmd, h, offset, bytes, blob); +} + +static int pmf_parser_rmw_cfg(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 maskbytes = pmf_next32(cmd); + u32 valuesbytes = pmf_next32(cmd); + u32 totalbytes = pmf_next32(cmd); + const void *maskblob = pmf_next_blob(cmd, maskbytes); + const void *valuesblob = pmf_next_blob(cmd, valuesbytes); + + LOG_PARSE("pmf: rmw_cfg(maskbytes: %ud, valuebytes: %ud," + " totalbytes: %d) ...\n", + maskbytes, valuesbytes, totalbytes); + LOG_BLOB("pmf: mask data: \n", maskblob, maskbytes); + LOG_BLOB("pmf: values data: \n", valuesblob, valuesbytes); + + PMF_PARSE_CALL(rmw_cfg, cmd, h, offset, maskbytes, valuesbytes, + totalbytes, maskblob, valuesblob); +} + + +static int pmf_parser_read_i2c_sub(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 subaddr = (u8)pmf_next32(cmd); + u32 bytes = pmf_next32(cmd); + + LOG_PARSE("pmf: read_i2c_sub(subaddr: %x, bytes: %ud)\n", + subaddr, bytes); + + PMF_PARSE_CALL(read_i2c_sub, cmd, h, subaddr, bytes); +} + +static int pmf_parser_write_i2c_sub(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 subaddr = (u8)pmf_next32(cmd); + u32 bytes = pmf_next32(cmd); + const void *blob = pmf_next_blob(cmd, bytes); + + LOG_PARSE("pmf: write_i2c_sub(subaddr: %x, bytes: %ud) ...\n", + subaddr, bytes); + LOG_BLOB("pmf: data: \n", blob, bytes); + + PMF_PARSE_CALL(write_i2c_sub, cmd, h, subaddr, bytes, blob); +} + +static int pmf_parser_set_i2c_mode(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u32 mode = pmf_next32(cmd); + + LOG_PARSE("pmf: set_i2c_mode(mode: %d)\n", mode); + + PMF_PARSE_CALL(set_i2c_mode, cmd, h, mode); +} + + +static int pmf_parser_rmw_i2c_sub(struct pmf_cmd *cmd, struct pmf_handlers *h) +{ + u8 subaddr = (u8)pmf_next32(cmd); + u32 maskbytes = pmf_next32(cmd); + u32 valuesbytes = pmf_next32(cmd); + u32 totalbytes = pmf_next32(cmd); + const void *maskblob = pmf_next_blob(cmd, maskbytes); + const void *valuesblob = pmf_next_blob(cmd, valuesbytes); + + LOG_PARSE("pmf: rmw_i2c_sub(subaddr: %x, maskbytes: %ud, valuebytes: %ud" + ", totalbytes: %d) ...\n", + subaddr, maskbytes, valuesbytes, totalbytes); + LOG_BLOB("pmf: mask data: \n", maskblob, maskbytes); + LOG_BLOB("pmf: values data: \n", valuesblob, valuesbytes); + + PMF_PARSE_CALL(rmw_i2c_sub, cmd, h, subaddr, maskbytes, valuesbytes, + totalbytes, maskblob, valuesblob); +} + +static int pmf_parser_read_reg32_msrx(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 xor = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg32_msrx(offset: %x, mask: %x, shift: %x," + " xor: %x\n", offset, mask, shift, xor); + + PMF_PARSE_CALL(read_reg32_msrx, cmd, h, offset, mask, shift, xor); +} + +static int pmf_parser_read_reg16_msrx(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 xor = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg16_msrx(offset: %x, mask: %x, shift: %x," + " xor: %x\n", offset, mask, shift, xor); + + PMF_PARSE_CALL(read_reg16_msrx, cmd, h, offset, mask, shift, xor); +} +static int pmf_parser_read_reg8_msrx(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 xor = pmf_next32(cmd); + + LOG_PARSE("pmf: read_reg8_msrx(offset: %x, mask: %x, shift: %x," + " xor: %x\n", offset, mask, shift, xor); + + PMF_PARSE_CALL(read_reg8_msrx, cmd, h, offset, mask, shift, xor); +} + +static int pmf_parser_write_reg32_slm(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg32_slm(offset: %x, shift: %x, mask: %x\n", + offset, shift, mask); + + PMF_PARSE_CALL(write_reg32_slm, cmd, h, offset, shift, mask); +} + +static int pmf_parser_write_reg16_slm(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg16_slm(offset: %x, shift: %x, mask: %x\n", + offset, shift, mask); + + PMF_PARSE_CALL(write_reg16_slm, cmd, h, offset, shift, mask); +} + +static int pmf_parser_write_reg8_slm(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 offset = pmf_next32(cmd); + u32 shift = pmf_next32(cmd); + u32 mask = pmf_next32(cmd); + + LOG_PARSE("pmf: write_reg8_slm(offset: %x, shift: %x, mask: %x\n", + offset, shift, mask); + + PMF_PARSE_CALL(write_reg8_slm, cmd, h, offset, shift, mask); +} + +static int pmf_parser_mask_and_compare(struct pmf_cmd *cmd, + struct pmf_handlers *h) +{ + u32 bytes = pmf_next32(cmd); + const void *maskblob = pmf_next_blob(cmd, bytes); + const void *valuesblob = pmf_next_blob(cmd, bytes); + + LOG_PARSE("pmf: mask_and_compare(length: %ud ...\n", bytes); + LOG_BLOB("pmf: mask data: \n", maskblob, bytes); + LOG_BLOB("pmf: values data: \n", valuesblob, bytes); + + PMF_PARSE_CALL(mask_and_compare, cmd, h, + bytes, maskblob, valuesblob); +} + + +typedef int (*pmf_cmd_parser_t)(struct pmf_cmd *cmd, struct pmf_handlers *h); + +static pmf_cmd_parser_t pmf_parsers[PMF_CMD_COUNT] = +{ + NULL, + pmf_parser_write_gpio, + pmf_parser_read_gpio, + pmf_parser_write_reg32, + pmf_parser_read_reg32, + pmf_parser_write_reg16, + pmf_parser_read_reg16, + pmf_parser_write_reg8, + pmf_parser_read_reg8, + pmf_parser_delay, + pmf_parser_wait_reg32, + pmf_parser_wait_reg16, + pmf_parser_wait_reg8, + pmf_parser_read_i2c, + pmf_parser_write_i2c, + pmf_parser_rmw_i2c, + NULL, /* Bogus command */ + NULL, /* Shift bytes right: NYI */ + NULL, /* Shift bytes left: NYI */ + pmf_parser_read_cfg, + pmf_parser_write_cfg, + pmf_parser_rmw_cfg, + pmf_parser_read_i2c_sub, + pmf_parser_write_i2c_sub, + pmf_parser_set_i2c_mode, + pmf_parser_rmw_i2c_sub, + pmf_parser_read_reg32_msrx, + pmf_parser_read_reg16_msrx, + pmf_parser_read_reg8_msrx, + pmf_parser_write_reg32_slm, + pmf_parser_write_reg16_slm, + pmf_parser_write_reg8_slm, + pmf_parser_mask_and_compare, +}; + +struct pmf_device { + struct list_head link; + struct device_node *node; + struct pmf_handlers *handlers; + struct list_head functions; + struct kref ref; +}; + +static LIST_HEAD(pmf_devices); +static spinlock_t pmf_lock = SPIN_LOCK_UNLOCKED; + +static void pmf_release_device(struct kref *kref) +{ + struct pmf_device *dev = container_of(kref, struct pmf_device, ref); + kfree(dev); +} + +static inline void pmf_put_device(struct pmf_device *dev) +{ + kref_put(&dev->ref, pmf_release_device); +} + +static inline struct pmf_device *pmf_get_device(struct pmf_device *dev) +{ + kref_get(&dev->ref); + return dev; +} + +static inline struct pmf_device *pmf_find_device(struct device_node *np) +{ + struct pmf_device *dev; + + list_for_each_entry(dev, &pmf_devices, link) { + if (dev->node == np) + return pmf_get_device(dev); + } + return NULL; +} + +static int pmf_parse_one(struct pmf_function *func, + struct pmf_handlers *handlers, + void *instdata, struct pmf_args *args) +{ + struct pmf_cmd cmd; + u32 ccode; + int count, rc; + + cmd.cmdptr = func->data; + cmd.cmdend = func->data + func->length; + cmd.func = func; + cmd.instdata = instdata; + cmd.args = args; + cmd.error = 0; + + LOG_PARSE("pmf: func %s, %d bytes, %s...\n", + func->name, func->length, + handlers ? "executing" : "parsing"); + + /* One subcommand to parse for now */ + count = 1; + + while(count-- && cmd.cmdptr < cmd.cmdend) { + /* Get opcode */ + ccode = pmf_next32(&cmd); + /* Check if we are hitting a command list, fetch new count */ + if (ccode == 0) { + count = pmf_next32(&cmd) - 1; + ccode = pmf_next32(&cmd); + } + if (cmd.error) { + LOG_ERROR("pmf: parse error, not enough data\n"); + return -ENXIO; + } + if (ccode >= PMF_CMD_COUNT) { + LOG_ERROR("pmf: command code %d unknown !\n", ccode); + return -ENXIO; + } + if (pmf_parsers[ccode] == NULL) { + LOG_ERROR("pmf: no parser for command %d !\n", ccode); + return -ENXIO; + } + rc = pmf_parsers[ccode](&cmd, handlers); + if (rc != 0) { + LOG_ERROR("pmf: parser for command %d returned" + " error %d\n", ccode, rc); + return rc; + } + } + + /* We are doing an initial parse pass, we need to adjust the size */ + if (handlers == NULL) + func->length = cmd.cmdptr - func->data; + + return 0; +} + +static int pmf_add_function_prop(struct pmf_device *dev, void *driverdata, + const char *name, u32 *data, + unsigned int length) +{ + int count = 0; + struct pmf_function *func = NULL; + + DBG("pmf: Adding functions for platform-do-%s\n", name); + + while (length >= 12) { + /* Allocate a structure */ + func = kzalloc(sizeof(struct pmf_function), GFP_KERNEL); + if (func == NULL) + goto bail; + kref_init(&func->ref); + INIT_LIST_HEAD(&func->irq_clients); + func->node = dev->node; + func->driver_data = driverdata; + func->name = name; + func->phandle = data[0]; + func->flags = data[1]; + data += 2; + length -= 8; + func->data = data; + func->length = length; + func->dev = dev; + DBG("pmf: idx %d: flags=%08x, phandle=%08x " + " %d bytes remaining, parsing...\n", + count+1, func->flags, func->phandle, length); + if (pmf_parse_one(func, NULL, NULL, NULL)) { + kfree(func); + goto bail; + } + length -= func->length; + data = (u32 *)(((u8 *)data) + func->length); + list_add(&func->link, &dev->functions); + pmf_get_device(dev); + count++; + } + bail: + DBG("pmf: Added %d functions\n", count); + + return count; +} + +static int pmf_add_functions(struct pmf_device *dev, void *driverdata) +{ + struct property *pp; +#define PP_PREFIX "platform-do-" + const int plen = strlen(PP_PREFIX); + int count = 0; + + for (pp = dev->node->properties; pp != 0; pp = pp->next) { + char *name; + if (strncmp(pp->name, PP_PREFIX, plen) != 0) + continue; + name = pp->name + plen; + if (strlen(name) && pp->length >= 12) + count += pmf_add_function_prop(dev, driverdata, name, + (u32 *)pp->value, + pp->length); + } + return count; +} + + +int pmf_register_driver(struct device_node *np, + struct pmf_handlers *handlers, + void *driverdata) +{ + struct pmf_device *dev; + unsigned long flags; + int rc = 0; + + if (handlers == NULL) + return -EINVAL; + + DBG("pmf: registering driver for node %s\n", np->full_name); + + spin_lock_irqsave(&pmf_lock, flags); + dev = pmf_find_device(np); + spin_unlock_irqrestore(&pmf_lock, flags); + if (dev != NULL) { + DBG("pmf: already there !\n"); + pmf_put_device(dev); + return -EBUSY; + } + + dev = kzalloc(sizeof(struct pmf_device), GFP_KERNEL); + if (dev == NULL) { + DBG("pmf: no memory !\n"); + return -ENOMEM; + } + kref_init(&dev->ref); + dev->node = of_node_get(np); + dev->handlers = handlers; + INIT_LIST_HEAD(&dev->functions); + + rc = pmf_add_functions(dev, driverdata); + if (rc == 0) { + DBG("pmf: no functions, disposing.. \n"); + of_node_put(np); + kfree(dev); + return -ENODEV; + } + + spin_lock_irqsave(&pmf_lock, flags); + list_add(&dev->link, &pmf_devices); + spin_unlock_irqrestore(&pmf_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(pmf_register_driver); + +struct pmf_function *pmf_get_function(struct pmf_function *func) +{ + if (!try_module_get(func->dev->handlers->owner)) + return NULL; + kref_get(&func->ref); + return func; +} +EXPORT_SYMBOL_GPL(pmf_get_function); + +static void pmf_release_function(struct kref *kref) +{ + struct pmf_function *func = + container_of(kref, struct pmf_function, ref); + pmf_put_device(func->dev); + kfree(func); +} + +static inline void __pmf_put_function(struct pmf_function *func) +{ + kref_put(&func->ref, pmf_release_function); +} + +void pmf_put_function(struct pmf_function *func) +{ + if (func == NULL) + return; + module_put(func->dev->handlers->owner); + __pmf_put_function(func); +} +EXPORT_SYMBOL_GPL(pmf_put_function); + +void pmf_unregister_driver(struct device_node *np) +{ + struct pmf_device *dev; + unsigned long flags; + + DBG("pmf: unregistering driver for node %s\n", np->full_name); + + spin_lock_irqsave(&pmf_lock, flags); + dev = pmf_find_device(np); + if (dev == NULL) { + DBG("pmf: not such driver !\n"); + spin_unlock_irqrestore(&pmf_lock, flags); + return; + } + list_del(&dev->link); + + while(!list_empty(&dev->functions)) { + struct pmf_function *func = + list_entry(dev->functions.next, typeof(*func), link); + list_del(&func->link); + __pmf_put_function(func); + } + + pmf_put_device(dev); + spin_unlock_irqrestore(&pmf_lock, flags); +} +EXPORT_SYMBOL_GPL(pmf_unregister_driver); + +struct pmf_function *__pmf_find_function(struct device_node *target, + const char *name, u32 flags) +{ + struct device_node *actor = of_node_get(target); + struct pmf_device *dev; + struct pmf_function *func, *result = NULL; + char fname[64]; + u32 *prop, ph; + + /* + * Look for a "platform-*" function reference. If we can't find + * one, then we fallback to a direct call attempt + */ + snprintf(fname, 63, "platform-%s", name); + prop = (u32 *)get_property(target, fname, NULL); + if (prop == NULL) + goto find_it; + ph = *prop; + if (ph == 0) + goto find_it; + + /* + * Ok, now try to find the actor. If we can't find it, we fail, + * there is no point in falling back there + */ + of_node_put(actor); + actor = of_find_node_by_phandle(ph); + if (actor == NULL) + return NULL; + find_it: + dev = pmf_find_device(actor); + if (dev == NULL) + return NULL; + + list_for_each_entry(func, &dev->functions, link) { + if (name && strcmp(name, func->name)) + continue; + if (func->phandle && target->node != func->phandle) + continue; + if ((func->flags & flags) == 0) + continue; + result = func; + break; + } + of_node_put(actor); + pmf_put_device(dev); + return result; +} + + +int pmf_register_irq_client(struct device_node *target, + const char *name, + struct pmf_irq_client *client) +{ + struct pmf_function *func; + unsigned long flags; + + spin_lock_irqsave(&pmf_lock, flags); + func = __pmf_find_function(target, name, PMF_FLAGS_INT_GEN); + if (func == NULL) { + spin_unlock_irqrestore(&pmf_lock, flags); + return -ENODEV; + } + list_add(&client->link, &func->irq_clients); + spin_unlock_irqrestore(&pmf_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(pmf_register_irq_client); + +void pmf_unregister_irq_client(struct device_node *np, + const char *name, + struct pmf_irq_client *client) +{ + unsigned long flags; + + spin_lock_irqsave(&pmf_lock, flags); + list_del(&client->link); + spin_unlock_irqrestore(&pmf_lock, flags); +} +EXPORT_SYMBOL_GPL(pmf_unregister_irq_client); + + +void pmf_do_irq(struct pmf_function *func) +{ + unsigned long flags; + struct pmf_irq_client *client; + + /* For now, using a spinlock over the whole function. Can be made + * to drop the lock using 2 lists if necessary + */ + spin_lock_irqsave(&pmf_lock, flags); + list_for_each_entry(client, &func->irq_clients, link) { + if (!try_module_get(client->owner)) + continue; + client->handler(client->data); + module_put(client->owner); + } + spin_unlock_irqrestore(&pmf_lock, flags); +} +EXPORT_SYMBOL_GPL(pmf_do_irq); + + +int pmf_call_one(struct pmf_function *func, struct pmf_args *args) +{ + struct pmf_device *dev = func->dev; + void *instdata = NULL; + int rc = 0; + + DBG(" ** pmf_call_one(%s/%s) **\n", dev->node->full_name, func->name); + + if (dev->handlers->begin) + instdata = dev->handlers->begin(func, args); + rc = pmf_parse_one(func, dev->handlers, instdata, args); + if (dev->handlers->end) + dev->handlers->end(func, instdata); + + return rc; +} +EXPORT_SYMBOL_GPL(pmf_call_one); + +int pmf_do_functions(struct device_node *np, const char *name, + u32 phandle, u32 fflags, struct pmf_args *args) +{ + struct pmf_device *dev; + struct pmf_function *func, *tmp; + unsigned long flags; + int rc = -ENODEV; + + spin_lock_irqsave(&pmf_lock, flags); + + dev = pmf_find_device(np); + if (dev == NULL) { + spin_unlock_irqrestore(&pmf_lock, flags); + return -ENODEV; + } + list_for_each_entry_safe(func, tmp, &dev->functions, link) { + if (name && strcmp(name, func->name)) + continue; + if (phandle && func->phandle && phandle != func->phandle) + continue; + if ((func->flags & fflags) == 0) + continue; + if (pmf_get_function(func) == NULL) + continue; + spin_unlock_irqrestore(&pmf_lock, flags); + rc = pmf_call_one(func, args); + pmf_put_function(func); + spin_lock_irqsave(&pmf_lock, flags); + } + pmf_put_device(dev); + spin_unlock_irqrestore(&pmf_lock, flags); + + return rc; +} +EXPORT_SYMBOL_GPL(pmf_do_functions); + + +struct pmf_function *pmf_find_function(struct device_node *target, + const char *name) +{ + struct pmf_function *func; + unsigned long flags; + + spin_lock_irqsave(&pmf_lock, flags); + func = __pmf_find_function(target, name, PMF_FLAGS_ON_DEMAND); + if (func) + func = pmf_get_function(func); + spin_unlock_irqrestore(&pmf_lock, flags); + return func; +} +EXPORT_SYMBOL_GPL(pmf_find_function); + +int pmf_call_function(struct device_node *target, const char *name, + struct pmf_args *args) +{ + struct pmf_function *func = pmf_find_function(target, name); + int rc; + + if (func == NULL) + return -ENODEV; + + rc = pmf_call_one(func, args); + pmf_put_function(func); + return rc; +} +EXPORT_SYMBOL_GPL(pmf_call_function); + Index: linux-work/include/asm-powerpc/pmac_feature.h =================================================================== --- linux-work.orig/include/asm-powerpc/pmac_feature.h 2006-01-07 10:53:21.000000000 +1100 +++ linux-work/include/asm-powerpc/pmac_feature.h 2006-01-07 10:54:03.000000000 +1100 @@ -374,5 +374,24 @@ extern struct macio_chip* macio_find(str #define MACIO_IN8(r) (in_8(MACIO_FCR8(macio,r))) #define MACIO_OUT8(r,v) (out_8(MACIO_FCR8(macio,r), (v))) +/* + * Those are exported by pmac feature for internal use by arch code + * only like the platform function callbacks, do not use directly in drivers + */ +extern spinlock_t feature_lock; +extern struct device_node *uninorth_node; +extern u32 __iomem *uninorth_base; + +/* + * Uninorth reg. access. Note that Uni-N regs are big endian + */ + +#define UN_REG(r) (uninorth_base + ((r) >> 2)) +#define UN_IN(r) (in_be32(UN_REG(r))) +#define UN_OUT(r,v) (out_be32(UN_REG(r), (v))) +#define UN_BIS(r,v) (UN_OUT((r), UN_IN(r) | (v))) +#define UN_BIC(r,v) (UN_OUT((r), UN_IN(r) & ~(v))) + + #endif /* __PPC_ASM_PMAC_FEATURE_H */ #endif /* __KERNEL__ */ Index: linux-work/arch/powerpc/platforms/powermac/smp.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/smp.c 2006-01-07 10:53:21.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/smp.c 2006-01-07 10:54:03.000000000 +1100 @@ -52,8 +52,9 @@ #include #include #include +#include -#undef DEBUG +#define DEBUG #ifdef DEBUG #define DBG(fmt...) udbg_printf(fmt) @@ -62,6 +63,7 @@ #endif extern void __secondary_start_pmac_0(void); +extern int pmac_pfunc_base_install(void); #ifdef CONFIG_PPC32 @@ -602,11 +604,29 @@ static void __init smp_core99_setup_i2c_ pmac_tb_clock_chip_host = NULL; } -#endif /* CONFIG_PPC64 */ /* - * SMP G4 and newer G5 use a GPIO to enable/disable the timebase. + * Newer G5s uses a platform function + */ + +static void smp_core99_pfunc_tb_freeze(int freeze) +{ + struct device_node *cpus; + struct pmf_args args; + + cpus = of_find_node_by_path("/cpus"); + BUG_ON(cpus == NULL); + args.count = 1; + args.u[0].v = !freeze; + pmf_call_function(cpus, "cpu-timebase", &args); + of_node_put(cpus); +} + +#else /* CONFIG_PPC64 */ + +/* + * SMP G4 use a GPIO to enable/disable the timebase. */ static unsigned int core99_tb_gpio; /* Timebase freeze GPIO */ @@ -620,6 +640,9 @@ static void smp_core99_gpio_tb_freeze(in pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, core99_tb_gpio, 0); } + +#endif /* !CONFIG_PPC64 */ + /* L2 and L3 cache settings to pass from CPU0 to CPU1 on G4 cpus */ volatile static long int core99_l2_cache; volatile static long int core99_l3_cache; @@ -665,19 +688,15 @@ static void __init smp_core99_setup(int machine_is_compatible("RackMac3,1")) smp_core99_setup_i2c_hwsync(ncpus); - /* GPIO based HW sync on recent G5s */ + /* pfunc based HW sync on recent G5s */ if (pmac_tb_freeze == NULL) { - struct device_node *np = - of_find_node_by_name(NULL, "timebase-enable"); - u32 *reg = (u32 *)get_property(np, "reg", NULL); - - if (np && reg && !strcmp(np->type, "gpio")) { - core99_tb_gpio = *reg; - if (core99_tb_gpio < 0x50) - core99_tb_gpio += 0x50; - pmac_tb_freeze = smp_core99_gpio_tb_freeze; + struct device_node *cpus = + of_find_node_by_path("/cpus"); + if (cpus && + get_property(cpus, "platform-cpu-timebase", NULL)) { + pmac_tb_freeze = smp_core99_pfunc_tb_freeze; printk(KERN_INFO "Processor timebase sync using" - " GPIO 0x%02x\n", core99_tb_gpio); + " platform function\n"); } } @@ -746,6 +765,7 @@ static int __init smp_core99_probe(void) /* We need to perform some early initialisations before we can start * setting up SMP as we are running before initcalls */ + pmac_pfunc_base_install(); pmac_i2c_init(); /* Setup various bits like timebase sync method, ability to nap, ... */ Index: linux-work/arch/powerpc/platforms/powermac/low_i2c.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/low_i2c.c 2006-01-07 10:53:21.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/low_i2c.c 2006-01-07 10:54:03.000000000 +1100 @@ -49,6 +49,7 @@ #include #include #include +#include #include #ifdef DEBUG @@ -1162,9 +1163,291 @@ int pmac_i2c_xfer(struct pmac_i2c_bus *b } EXPORT_SYMBOL_GPL(pmac_i2c_xfer); +/* some quirks for platform function decoding */ +enum { + pmac_i2c_quirk_invmask = 0x00000001u, +}; + +static void pmac_i2c_devscan(void (*callback)(struct device_node *dev, + int quirks)) +{ + struct pmac_i2c_bus *bus; + struct device_node *np; + static struct whitelist_ent { + char *name; + char *compatible; + int quirks; + } whitelist[] = { + /* XXX Study device-tree's & apple drivers are get the quirks + * right ! + */ + { "i2c-hwclock", NULL, pmac_i2c_quirk_invmask }, + { "i2c-cpu-voltage", NULL, 0}, + { "temp-monitor", NULL, 0 }, + { "supply-monitor", NULL, 0 }, + { NULL, NULL, 0 }, + }; + + /* Only some devices need to have platform functions instanciated + * here. For now, we have a table. Others, like 9554 i2c GPIOs used + * on Xserve, if we ever do a driver for them, will use their own + * platform function instance + */ + list_for_each_entry(bus, &pmac_i2c_busses, link) { + for (np = NULL; + (np = of_get_next_child(bus->busnode, np)) != NULL;) { + struct whitelist_ent *p; + /* If multibus, check if device is on that bus */ + if (bus->flags & pmac_i2c_multibus) + if (bus != pmac_i2c_find_bus(np)) + continue; + for (p = whitelist; p->name != NULL; p++) { + if (strcmp(np->name, p->name)) + continue; + if (p->compatible && + !device_is_compatible(np, p->compatible)) + continue; + callback(np, p->quirks); + break; + } + } + } +} + +#define MAX_I2C_DATA 64 + +struct pmac_i2c_pf_inst +{ + struct pmac_i2c_bus *bus; + u8 addr; + u8 buffer[MAX_I2C_DATA]; + u8 scratch[MAX_I2C_DATA]; + int bytes; + int quirks; +}; + +static void* pmac_i2c_do_begin(struct pmf_function *func, struct pmf_args *args) +{ + struct pmac_i2c_pf_inst *inst; + struct pmac_i2c_bus *bus; + + bus = pmac_i2c_find_bus(func->node); + if (bus == NULL) { + printk(KERN_ERR "low_i2c: Can't find bus for %s (pfunc)\n", + func->node->full_name); + return NULL; + } + if (pmac_i2c_open(bus, 0)) { + printk(KERN_ERR "low_i2c: Can't open i2c bus for %s (pfunc)\n", + func->node->full_name); + return NULL; + } + + /* XXX might need GFP_ATOMIC when called during the suspend process, + * but then, there are already lots of issues with suspending when + * near OOM that need to be resolved, the allocator itself should + * probably make GFP_NOIO implicit during suspend + */ + inst = kzalloc(sizeof(struct pmac_i2c_pf_inst), GFP_KERNEL); + if (inst == NULL) { + pmac_i2c_close(bus); + return NULL; + } + inst->bus = bus; + inst->addr = pmac_i2c_get_dev_addr(func->node); + inst->quirks = (int)(long)func->driver_data; + return inst; +} + +static void pmac_i2c_do_end(struct pmf_function *func, void *instdata) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + if (inst == NULL) + return; + pmac_i2c_close(inst->bus); + if (inst) + kfree(inst); +} + +static int pmac_i2c_do_read(PMF_STD_ARGS, u32 len) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + inst->bytes = len; + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_read, 0, 0, + inst->buffer, len); +} + +static int pmac_i2c_do_write(PMF_STD_ARGS, u32 len, const u8 *data) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_write, 0, 0, + (u8 *)data, len); +} + +/* This function is used to do the masking & OR'ing for the "rmw" type + * callbacks. Ze should apply the mask and OR in the values in the + * buffer before writing back. The problem is that it seems that + * various darwin drivers implement the mask/or differently, thus + * we need to check the quirks first + */ +static void pmac_i2c_do_apply_rmw(struct pmac_i2c_pf_inst *inst, + u32 len, const u8 *mask, const u8 *val) +{ + int i; + + if (inst->quirks & pmac_i2c_quirk_invmask) { + for (i = 0; i < len; i ++) + inst->scratch[i] = (inst->buffer[i] & mask[i]) | val[i]; + } else { + for (i = 0; i < len; i ++) + inst->scratch[i] = (inst->buffer[i] & ~mask[i]) + | (val[i] & mask[i]); + } +} + +static int pmac_i2c_do_rmw(PMF_STD_ARGS, u32 masklen, u32 valuelen, + u32 totallen, const u8 *maskdata, + const u8 *valuedata) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + if (masklen > inst->bytes || valuelen > inst->bytes || + totallen > inst->bytes || valuelen > masklen) + return -EINVAL; + + pmac_i2c_do_apply_rmw(inst, masklen, maskdata, valuedata); + + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_write, 0, 0, + inst->scratch, totallen); +} + +static int pmac_i2c_do_read_sub(PMF_STD_ARGS, u8 subaddr, u32 len) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + inst->bytes = len; + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_read, 1, subaddr, + inst->buffer, len); +} + +static int pmac_i2c_do_write_sub(PMF_STD_ARGS, u8 subaddr, u32 len, + const u8 *data) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_write, 1, + subaddr, (u8 *)data, len); +} + +static int pmac_i2c_do_set_mode(PMF_STD_ARGS, int mode) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + return pmac_i2c_setmode(inst->bus, mode); +} + +static int pmac_i2c_do_rmw_sub(PMF_STD_ARGS, u8 subaddr, u32 masklen, + u32 valuelen, u32 totallen, const u8 *maskdata, + const u8 *valuedata) +{ + struct pmac_i2c_pf_inst *inst = instdata; + + if (masklen > inst->bytes || valuelen > inst->bytes || + totallen > inst->bytes || valuelen > masklen) + return -EINVAL; + + pmac_i2c_do_apply_rmw(inst, masklen, maskdata, valuedata); + + return pmac_i2c_xfer(inst->bus, inst->addr | pmac_i2c_write, 1, + subaddr, inst->scratch, totallen); +} + +static int pmac_i2c_do_mask_and_comp(PMF_STD_ARGS, u32 len, + const u8 *maskdata, + const u8 *valuedata) +{ + struct pmac_i2c_pf_inst *inst = instdata; + int i, match; + + /* Get return value pointer, it's assumed to be a u32 */ + if (!args || !args->count || !args->u[0].p) + return -EINVAL; + + /* Check buffer */ + if (len > inst->bytes) + return -EINVAL; + + for (i = 0, match = 1; match && i < len; i ++) + if ((inst->buffer[i] & maskdata[i]) != valuedata[i]) + match = 0; + *args->u[0].p = match; + return 0; +} + +static int pmac_i2c_do_delay(PMF_STD_ARGS, u32 duration) +{ + msleep((duration + 999) / 1000); + return 0; +} + + +static struct pmf_handlers pmac_i2c_pfunc_handlers = { + .begin = pmac_i2c_do_begin, + .end = pmac_i2c_do_end, + .read_i2c = pmac_i2c_do_read, + .write_i2c = pmac_i2c_do_write, + .rmw_i2c = pmac_i2c_do_rmw, + .read_i2c_sub = pmac_i2c_do_read_sub, + .write_i2c_sub = pmac_i2c_do_write_sub, + .rmw_i2c_sub = pmac_i2c_do_rmw_sub, + .set_i2c_mode = pmac_i2c_do_set_mode, + .mask_and_compare = pmac_i2c_do_mask_and_comp, + .delay = pmac_i2c_do_delay, +}; + +static void __init pmac_i2c_dev_create(struct device_node *np, int quirks) +{ + DBG("dev_create(%s)\n", np->full_name); + + pmf_register_driver(np, &pmac_i2c_pfunc_handlers, + (void *)(long)quirks); +} + +static void __init pmac_i2c_dev_init(struct device_node *np, int quirks) +{ + DBG("dev_create(%s)\n", np->full_name); + + pmf_do_functions(np, NULL, 0, PMF_FLAGS_ON_INIT, NULL); +} + +static void pmac_i2c_dev_suspend(struct device_node *np, int quirks) +{ + DBG("dev_suspend(%s)\n", np->full_name); + pmf_do_functions(np, NULL, 0, PMF_FLAGS_ON_SLEEP, NULL); +} + +static void pmac_i2c_dev_resume(struct device_node *np, int quirks) +{ + DBG("dev_resume(%s)\n", np->full_name); + pmf_do_functions(np, NULL, 0, PMF_FLAGS_ON_WAKE, NULL); +} + +void pmac_pfunc_i2c_suspend(void) +{ + pmac_i2c_devscan(pmac_i2c_dev_suspend); +} + +void pmac_pfunc_i2c_resume(void) +{ + pmac_i2c_devscan(pmac_i2c_dev_resume); +} + /* - * Initialize us: probe all i2c busses on the machine and instantiate - * busses. + * Initialize us: probe all i2c busses on the machine, instantiate + * busses and platform functions as needed. */ /* This is non-static as it might be called early by smp code */ int __init pmac_i2c_init(void) @@ -1187,6 +1470,10 @@ int __init pmac_i2c_init(void) /* Probe SMU i2c busses */ smu_i2c_probe(); #endif + + /* Now add plaform functions for some known devices */ + pmac_i2c_devscan(pmac_i2c_dev_create); + return 0; } arch_initcall(pmac_i2c_init); @@ -1216,6 +1503,9 @@ static int __init pmac_i2c_create_platfo platform_device_add(bus->platform_dev); } + /* Now call platform "init" functions */ + pmac_i2c_devscan(pmac_i2c_dev_init); + return 0; } subsys_initcall(pmac_i2c_create_platform_devices); Index: linux-work/drivers/macintosh/via-pmu.c =================================================================== --- linux-work.orig/drivers/macintosh/via-pmu.c 2006-01-07 10:53:21.000000000 +1100 +++ linux-work/drivers/macintosh/via-pmu.c 2006-01-07 10:54:03.000000000 +1100 @@ -55,6 +55,8 @@ #include #include #include +#include +#include #include #include #include @@ -2105,6 +2107,10 @@ pmac_suspend_devices(void) return -EBUSY; } + /* Call platform functions marked "on sleep" */ + pmac_pfunc_i2c_suspend(); + pmac_pfunc_base_suspend(); + /* Stop preemption */ preempt_disable(); @@ -2175,6 +2181,10 @@ pmac_wakeup_devices(void) mdelay(10); preempt_enable(); + /* Call platform functions marked "on wake" */ + pmac_pfunc_base_resume(); + pmac_pfunc_i2c_resume(); + /* Resume devices */ device_resume(); Index: linux-work/include/asm-powerpc/pmac_low_i2c.h =================================================================== --- linux-work.orig/include/asm-powerpc/pmac_low_i2c.h 2006-01-07 10:53:21.000000000 +1100 +++ linux-work/include/asm-powerpc/pmac_low_i2c.h 2006-01-07 10:54:03.000000000 +1100 @@ -99,6 +99,9 @@ extern int pmac_i2c_setmode(struct pmac_ extern int pmac_i2c_xfer(struct pmac_i2c_bus *bus, u8 addrdir, int subsize, u32 subaddr, u8 *data, int len); +/* Suspend/resume code called by via-pmu directly for now */ +extern void pmac_pfunc_i2c_suspend(void); +extern void pmac_pfunc_i2c_resume(void); #endif /* __KERNEL__ */ #endif /* __PMAC_LOW_I2C_H__ */ From benh at kernel.crashing.org Sat Jan 7 11:45:28 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sat, 07 Jan 2006 11:45:28 +1100 Subject: [PATCH] 4/5 powerpc: Add cpufreq support for all desktop G5 Message-ID: <1136594728.4840.180.camel@localhost.localdomain> This patch adds cpufreq support for all desktop "tower" G5 models. The only G5 models still lacking cpufreq support at this point are the Xserve and possibly the new iMac iSight (not tested). I'll have those added soon. That patch uses the new platform functions interpreter to implement frequency and voltage switching on most models. Note that in order to find the low frequency value, I had to hack something that might now work properly on all models, so if the frequency value reported when running low speed looks bogus to you, please report it to me. (Appart from a bogus reported value, things should work fine). Signed-off-by: Benjamin Herrenschmidt Index: linux-work/arch/powerpc/platforms/powermac/cpufreq_64.c =================================================================== --- linux-work.orig/arch/powerpc/platforms/powermac/cpufreq_64.c 2006-01-07 10:53:19.000000000 +1100 +++ linux-work/arch/powerpc/platforms/powermac/cpufreq_64.c 2006-01-07 10:54:25.000000000 +1100 @@ -28,6 +28,7 @@ #include #include #include +#include #undef DEBUG @@ -85,6 +86,10 @@ static u32 *g5_pmode_data; static int g5_pmode_max; static int g5_pmode_cur; +static void (*g5_switch_volt)(int speed_mode); +static int (*g5_switch_freq)(int speed_mode); +static int (*g5_query_freq)(void); + static DECLARE_MUTEX(g5_switch_mutex); @@ -92,9 +97,11 @@ static struct smu_sdbp_fvt *g5_fvt_table static int g5_fvt_count; /* number of op. points */ static int g5_fvt_cur; /* current op. point */ -/* ----------------- real hardware interface */ +/* + * SMU based voltage switching for Neo2 platforms + */ -static void g5_switch_volt(int speed_mode) +static void g5_smu_switch_volt(int speed_mode) { struct smu_simple_cmd cmd; @@ -105,26 +112,57 @@ static void g5_switch_volt(int speed_mod wait_for_completion(&comp); } -static int g5_switch_freq(int speed_mode) -{ - struct cpufreq_freqs freqs; - int to; +/* + * Platform function based voltage/vdnap switching for Neo2 + */ - if (g5_pmode_cur == speed_mode) - return 0; +static struct pmf_function *pfunc_set_vdnap0; +static struct pmf_function *pfunc_vdnap0_complete; - down(&g5_switch_mutex); +static void g5_vdnap_switch_volt(int speed_mode) +{ + struct pmf_args args; + u32 slew, done = 0; + unsigned long timeout; + + slew = (speed_mode == CPUFREQ_LOW) ? 1 : 0; + args.count = 1; + args.u[0].p = &slew; + + pmf_call_one(pfunc_set_vdnap0, &args); + + /* It's an irq GPIO so we should be able to just block here, + * I'll do that later after I've properly tested the IRQ code for + * platform functions + */ + timeout = jiffies + HZ/10; + while(!time_after(jiffies, timeout)) { + args.count = 1; + args.u[0].p = &done; + pmf_call_one(pfunc_vdnap0_complete, &args); + if (done) + break; + msleep(1); + } + if (done == 0) + printk(KERN_WARNING "cpufreq: Timeout in clock slewing !\n"); +} - freqs.old = g5_cpu_freqs[g5_pmode_cur].frequency; - freqs.new = g5_cpu_freqs[speed_mode].frequency; - freqs.cpu = 0; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); +/* + * SCOM based frequency switching for 970FX rev3 + */ +static int g5_scom_switch_freq(int speed_mode) +{ + unsigned long flags; + int to; /* If frequency is going up, first ramp up the voltage */ if (speed_mode < g5_pmode_cur) g5_switch_volt(speed_mode); + local_irq_save(flags); + /* Clear PCR high */ scom970_write(SCOM_PCR, 0); /* Clear PCR low */ @@ -147,6 +185,8 @@ static int g5_switch_freq(int speed_mode udelay(100); } + local_irq_restore(flags); + /* If frequency is going down, last ramp the voltage */ if (speed_mode > g5_pmode_cur) g5_switch_volt(speed_mode); @@ -154,14 +194,10 @@ static int g5_switch_freq(int speed_mode g5_pmode_cur = speed_mode; ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - up(&g5_switch_mutex); - return 0; } -static int g5_query_freq(void) +static int g5_scom_query_freq(void) { unsigned long psr = scom970_read(SCOM_PSR); int i; @@ -173,7 +209,104 @@ static int g5_query_freq(void) return i; } -/* ----------------- cpufreq bookkeeping */ +/* + * Platform function based voltage switching for PowerMac7,2 & 7,3 + */ + +static struct pmf_function *pfunc_cpu0_volt_high; +static struct pmf_function *pfunc_cpu0_volt_low; +static struct pmf_function *pfunc_cpu1_volt_high; +static struct pmf_function *pfunc_cpu1_volt_low; + +static void g5_pfunc_switch_volt(int speed_mode) +{ + if (speed_mode == CPUFREQ_HIGH) { + if (pfunc_cpu0_volt_high) + pmf_call_one(pfunc_cpu0_volt_high, NULL); + if (pfunc_cpu1_volt_high) + pmf_call_one(pfunc_cpu1_volt_high, NULL); + } else { + if (pfunc_cpu0_volt_low) + pmf_call_one(pfunc_cpu0_volt_low, NULL); + if (pfunc_cpu1_volt_low) + pmf_call_one(pfunc_cpu1_volt_low, NULL); + } + msleep(10); /* should be faster , to fix */ +} + +/* + * Platform function based frequency switching for PowerMac7,2 & 7,3 + */ + +static struct pmf_function *pfunc_cpu_setfreq_high; +static struct pmf_function *pfunc_cpu_setfreq_low; +static struct pmf_function *pfunc_cpu_getfreq; +static struct pmf_function *pfunc_slewing_done;; + +static int g5_pfunc_switch_freq(int speed_mode) +{ + struct pmf_args args; + u32 done = 0; + unsigned long timeout; + + /* If frequency is going up, first ramp up the voltage */ + if (speed_mode < g5_pmode_cur) + g5_switch_volt(speed_mode); + + /* Do it */ + if (speed_mode == CPUFREQ_HIGH) + pmf_call_one(pfunc_cpu_setfreq_high, NULL); + else + pmf_call_one(pfunc_cpu_setfreq_low, NULL); + + /* It's an irq GPIO so we should be able to just block here, + * I'll do that later after I've properly tested the IRQ code for + * platform functions + */ + timeout = jiffies + HZ/10; + while(!time_after(jiffies, timeout)) { + args.count = 1; + args.u[0].p = &done; + pmf_call_one(pfunc_slewing_done, &args); + if (done) + break; + msleep(1); + } + if (done == 0) + printk(KERN_WARNING "cpufreq: Timeout in clock slewing !\n"); + + /* If frequency is going down, last ramp the voltage */ + if (speed_mode > g5_pmode_cur) + g5_switch_volt(speed_mode); + + g5_pmode_cur = speed_mode; + ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul; + + return 0; +} + +static int g5_pfunc_query_freq(void) +{ + struct pmf_args args; + u32 val = 0; + + args.count = 1; + args.u[0].p = &val; + pmf_call_one(pfunc_cpu_getfreq, &args); + return val ? CPUFREQ_HIGH : CPUFREQ_LOW; +} + +/* + * Fake voltage switching for platforms with missing support + */ + +static void g5_dummy_switch_volt(int speed_mode) +{ +} + +/* + * Common interface to the cpufreq core + */ static int g5_cpufreq_verify(struct cpufreq_policy *policy) { @@ -183,13 +316,30 @@ static int g5_cpufreq_verify(struct cpuf static int g5_cpufreq_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation) { - unsigned int newstate = 0; + unsigned int newstate = 0; + struct cpufreq_freqs freqs; + int rc; if (cpufreq_frequency_table_target(policy, g5_cpu_freqs, target_freq, relation, &newstate)) return -EINVAL; - return g5_switch_freq(newstate); + if (g5_pmode_cur == newstate) + return 0; + + down(&g5_switch_mutex); + + freqs.old = g5_cpu_freqs[g5_pmode_cur].frequency; + freqs.new = g5_cpu_freqs[newstate].frequency; + freqs.cpu = 0; + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + rc = g5_switch_freq(newstate); + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + up(&g5_switch_mutex); + + return rc; } static unsigned int g5_cpufreq_get_speed(unsigned int cpu) @@ -205,6 +355,7 @@ static int g5_cpufreq_cpu_init(struct cp policy->governor = CPUFREQ_DEFAULT_GOVERNOR; policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; policy->cur = g5_cpu_freqs[g5_query_freq()].frequency; + policy->cpus = cpu_possible_map; cpufreq_frequency_table_get_attr(g5_cpu_freqs, policy->cpu); return cpufreq_frequency_table_cpuinfo(policy, @@ -224,19 +375,39 @@ static struct cpufreq_driver g5_cpufreq_ }; -static int __init g5_cpufreq_init(void) +static int __init g5_neo2_cpufreq_init(struct device_node *cpus) { struct device_node *cpunode; unsigned int psize, ssize; - struct smu_sdbp_header *shdr; unsigned long max_freq; - u32 *valp; + char *freq_method, *volt_method; + u32 *valp, pvr_hi; + int use_volts_vdnap = 0; + int use_volts_smu = 0; int rc = -ENODEV; - /* Look for CPU and SMU nodes */ - cpunode = of_find_node_by_type(NULL, "cpu"); - if (!cpunode) { - DBG("No CPU node !\n"); + /* Check supported platforms */ + if (machine_is_compatible("PowerMac8,1") || + machine_is_compatible("PowerMac8,2") || + machine_is_compatible("PowerMac9,1")) + use_volts_smu = 1; + else if (machine_is_compatible("PowerMac11,2")) + use_volts_vdnap = 1; + else + return -ENODEV; + + /* Get first CPU node */ + for (cpunode = NULL; + (cpunode = of_get_next_child(cpus, cpunode)) != NULL;) { + u32 *reg = + (u32 *)get_property(cpunode, "reg", NULL); + if (reg == NULL || (*reg) != 0) + continue; + if (!strcmp(cpunode->type, "cpu")) + break; + } + if (cpunode == NULL) { + printk(KERN_ERR "cpufreq: Can't find any CPU 0 node\n"); return -ENODEV; } @@ -246,8 +417,9 @@ static int __init g5_cpufreq_init(void) DBG("No cpu-version property !\n"); goto bail_noprops; } - if (((*valp) >> 16) != 0x3c) { - DBG("Wrong CPU version: %08x\n", *valp); + pvr_hi = (*valp) >> 16; + if (pvr_hi != 0x3c && pvr_hi != 0x44) { + printk(KERN_ERR "cpufreq: Unsupported CPU version\n"); goto bail_noprops; } @@ -259,18 +431,50 @@ static int __init g5_cpufreq_init(void) } g5_pmode_max = psize / sizeof(u32) - 1; - /* Look for the FVT table */ - shdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); - if (!shdr) - goto bail_noprops; - g5_fvt_table = (struct smu_sdbp_fvt *)&shdr[1]; - ssize = (shdr->len * sizeof(u32)) - sizeof(struct smu_sdbp_header); - g5_fvt_count = ssize / sizeof(struct smu_sdbp_fvt); - g5_fvt_cur = 0; + if (use_volts_smu) { + struct smu_sdbp_header *shdr; - /* Sanity checking */ - if (g5_fvt_count < 1 || g5_pmode_max < 1) - goto bail_noprops; + /* Look for the FVT table */ + shdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); + if (!shdr) + goto bail_noprops; + g5_fvt_table = (struct smu_sdbp_fvt *)&shdr[1]; + ssize = (shdr->len * sizeof(u32)) - + sizeof(struct smu_sdbp_header); + g5_fvt_count = ssize / sizeof(struct smu_sdbp_fvt); + g5_fvt_cur = 0; + + /* Sanity checking */ + if (g5_fvt_count < 1 || g5_pmode_max < 1) + goto bail_noprops; + + g5_switch_volt = g5_smu_switch_volt; + volt_method = "SMU"; + } else if (use_volts_vdnap) { + struct device_node *root; + + root = of_find_node_by_path("/"); + if (root == NULL) { + printk(KERN_ERR "cpufreq: Can't find root of " + "device tree\n"); + goto bail_noprops; + } + pfunc_set_vdnap0 = pmf_find_function(root, "set-vdnap0"); + pfunc_vdnap0_complete = + pmf_find_function(root, "slewing-done"); + if (pfunc_set_vdnap0 == NULL || + pfunc_vdnap0_complete == NULL) { + printk(KERN_ERR "cpufreq: Can't find required " + "platform function\n"); + goto bail_noprops; + } + + g5_switch_volt = g5_vdnap_switch_volt; + volt_method = "GPIO"; + } else { + g5_switch_volt = g5_dummy_switch_volt; + volt_method = "none"; + } /* * From what I see, clock-frequency is always the maximal frequency. @@ -286,19 +490,23 @@ static int __init g5_cpufreq_init(void) g5_cpu_freqs[0].frequency = max_freq; g5_cpu_freqs[1].frequency = max_freq/2; - /* Check current frequency */ - g5_pmode_cur = g5_query_freq(); - if (g5_pmode_cur > 1) - /* We don't support anything but 1:1 and 1:2, fixup ... */ - g5_pmode_cur = 1; + /* Set callbacks */ + g5_switch_freq = g5_scom_switch_freq; + g5_query_freq = g5_scom_query_freq; + freq_method = "SCOM"; /* Force apply current frequency to make sure everything is in * sync (voltage is right for example). Firmware may leave us with * a strange setting ... */ - g5_switch_freq(g5_pmode_cur); + g5_switch_volt(CPUFREQ_HIGH); + msleep(10); + g5_pmode_cur = -1; + g5_switch_freq(g5_query_freq()); printk(KERN_INFO "Registering G5 CPU frequency driver\n"); + printk(KERN_INFO "Frequency method: %s, Voltage method: %s\n", + freq_method, volt_method); printk(KERN_INFO "Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n", g5_cpu_freqs[1].frequency/1000, g5_cpu_freqs[0].frequency/1000, @@ -317,6 +525,200 @@ static int __init g5_cpufreq_init(void) return rc; } +static int __init g5_pm72_cpufreq_init(struct device_node *cpus) +{ + struct device_node *cpuid = NULL, *hwclock = NULL, *cpunode = NULL; + u8 *eeprom = NULL; + u32 *valp; + u64 max_freq, min_freq, ih, il; + int has_volt = 1, rc = 0; + + /* Get first CPU node */ + for (cpunode = NULL; + (cpunode = of_get_next_child(cpus, cpunode)) != NULL;) { + if (!strcmp(cpunode->type, "cpu")) + break; + } + if (cpunode == NULL) { + printk(KERN_ERR "cpufreq: Can't find any CPU node\n"); + return -ENODEV; + } + + /* Lookup the cpuid eeprom node */ + cpuid = of_find_node_by_path("/u3 at 0,f8000000/i2c at f8001000/cpuid at a0"); + if (cpuid != NULL) + eeprom = (u8 *)get_property(cpuid, "cpuid", NULL); + if (eeprom == NULL) { + printk(KERN_ERR "cpufreq: Can't find cpuid EEPROM !\n"); + rc = -ENODEV; + goto bail; + } + + /* Lookup the i2c hwclock */ + for (hwclock = NULL; + (hwclock = of_find_node_by_name(hwclock, "i2c-hwclock")) != NULL;){ + char *loc = get_property(hwclock, "hwctrl-location", NULL); + if (loc == NULL) + continue; + if (strcmp(loc, "CPU CLOCK")) + continue; + if (!get_property(hwclock, "platform-get-frequency", NULL)) + continue; + break; + } + if (hwclock == NULL) { + printk(KERN_ERR "cpufreq: Can't find i2c clock chip !\n"); + rc = -ENODEV; + goto bail; + } + + DBG("cpufreq: i2c clock chip found: %s\n", hwclock->full_name); + + /* Now get all the platform functions */ + pfunc_cpu_getfreq = + pmf_find_function(hwclock, "get-frequency"); + pfunc_cpu_setfreq_high = + pmf_find_function(hwclock, "set-frequency-high"); + pfunc_cpu_setfreq_low = + pmf_find_function(hwclock, "set-frequency-low"); + pfunc_slewing_done = + pmf_find_function(hwclock, "slewing-done"); + pfunc_cpu0_volt_high = + pmf_find_function(hwclock, "set-voltage-high-0"); + pfunc_cpu0_volt_low = + pmf_find_function(hwclock, "set-voltage-low-0"); + pfunc_cpu1_volt_high = + pmf_find_function(hwclock, "set-voltage-high-1"); + pfunc_cpu1_volt_low = + pmf_find_function(hwclock, "set-voltage-low-1"); + + /* Check we have minimum requirements */ + if (pfunc_cpu_getfreq == NULL || pfunc_cpu_setfreq_high == NULL || + pfunc_cpu_setfreq_low == NULL || pfunc_slewing_done == NULL) { + printk(KERN_ERR "cpufreq: Can't find platform functions !\n"); + rc = -ENODEV; + goto bail; + } + + /* Check that we have complete sets */ + if (pfunc_cpu0_volt_high == NULL || pfunc_cpu0_volt_low == NULL) { + pmf_put_function(pfunc_cpu0_volt_high); + pmf_put_function(pfunc_cpu0_volt_low); + pfunc_cpu0_volt_high = pfunc_cpu0_volt_low = NULL; + has_volt = 0; + } + if (!has_volt || + pfunc_cpu1_volt_high == NULL || pfunc_cpu1_volt_low == NULL) { + pmf_put_function(pfunc_cpu1_volt_high); + pmf_put_function(pfunc_cpu1_volt_low); + pfunc_cpu1_volt_high = pfunc_cpu1_volt_low = NULL; + } + + /* Note: The device tree also contains a "platform-set-values" + * function for which I haven't quite figured out the usage. It + * might have to be called on init and/or wakeup, I'm not too sure + * but things seem to work fine without it so far ... + */ + + /* Get max frequency from device-tree */ + valp = (u32 *)get_property(cpunode, "clock-frequency", NULL); + if (!valp) { + printk(KERN_ERR "cpufreq: Can't find CPU frequency !\n"); + rc = -ENODEV; + goto bail; + } + + max_freq = (*valp)/1000; + + /* Now calculate reduced frequency by using the cpuid input freq + * ratio. This requires 64 bits math unless we are willing to lose + * some precision + */ + ih = *((u32 *)(eeprom + 0x10)); + il = *((u32 *)(eeprom + 0x20)); + min_freq = 0; + if (ih != 0 && il != 0) + min_freq = (max_freq * il) / ih; + + /* Sanity check */ + if (min_freq >= max_freq || min_freq < 1000) { + printk(KERN_ERR "cpufreq: Can't calculate low frequency !\n"); + rc = -ENODEV; + goto bail; + } + g5_cpu_freqs[0].frequency = max_freq; + g5_cpu_freqs[1].frequency = min_freq; + + /* Set callbacks */ + g5_switch_volt = g5_pfunc_switch_volt; + g5_switch_freq = g5_pfunc_switch_freq; + g5_query_freq = g5_pfunc_query_freq; + + /* Force apply current frequency to make sure everything is in + * sync (voltage is right for example). Firmware may leave us with + * a strange setting ... + */ + g5_switch_volt(CPUFREQ_HIGH); + msleep(10); + g5_pmode_cur = -1; + g5_switch_freq(g5_query_freq()); + + printk(KERN_INFO "Registering G5 CPU frequency driver\n"); + printk(KERN_INFO "Frequency method: i2c/pfunc, " + "Voltage method: %s\n", has_volt ? "i2c/pfunc" : "none"); + printk(KERN_INFO "Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n", + g5_cpu_freqs[1].frequency/1000, + g5_cpu_freqs[0].frequency/1000, + g5_cpu_freqs[g5_pmode_cur].frequency/1000); + + rc = cpufreq_register_driver(&g5_cpufreq_driver); + bail: + if (rc != 0) { + pmf_put_function(pfunc_cpu_getfreq); + pmf_put_function(pfunc_cpu_setfreq_high); + pmf_put_function(pfunc_cpu_setfreq_low); + pmf_put_function(pfunc_slewing_done); + pmf_put_function(pfunc_cpu0_volt_high); + pmf_put_function(pfunc_cpu0_volt_low); + pmf_put_function(pfunc_cpu1_volt_high); + pmf_put_function(pfunc_cpu1_volt_low); + } + of_node_put(hwclock); + of_node_put(cpuid); + of_node_put(cpunode); + + return rc; +} + +static int __init g5_rm31_cpufreq_init(struct device_node *cpus) +{ + /* NYI */ + return 0; +} + +static int __init g5_cpufreq_init(void) +{ + struct device_node *cpus; + int rc; + + cpus = of_find_node_by_path("/cpus"); + if (cpus == NULL) { + DBG("No /cpus node !\n"); + return -ENODEV; + } + + if (machine_is_compatible("PowerMac7,2") || + machine_is_compatible("PowerMac7,3")) + rc = g5_pm72_cpufreq_init(cpus); + else if (machine_is_compatible("RackMac3,1")) + rc = g5_rm31_cpufreq_init(cpus); + else + rc = g5_neo2_cpufreq_init(cpus); + + of_node_put(cpus); + return rc; +} + module_init(g5_cpufreq_init); From benh at kernel.crashing.org Sat Jan 7 11:46:37 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sat, 07 Jan 2006 11:46:37 +1100 Subject: [PATCH] 5/5 powerpc: Update g5 defconfig Message-ID: <1136594797.4840.183.camel@localhost.localdomain> This is just a deconfig update so that the Quad G5 works better with the default config. Signed-off-by: Benjamin Herrenschmidt Index: linux-work/arch/powerpc/configs/g5_defconfig =================================================================== --- linux-work.orig/arch/powerpc/configs/g5_defconfig 2005-11-24 17:18:42.000000000 +1100 +++ linux-work/arch/powerpc/configs/g5_defconfig 2006-01-06 15:03:26.000000000 +1100 @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit -# Linux kernel version: 2.6.15-rc1 -# Tue Nov 15 14:39:20 2005 +# Linux kernel version: 2.6.15-rc6 +# Fri Jan 6 15:03:09 2006 # CONFIG_PPC64=y CONFIG_64BIT=y @@ -26,7 +26,7 @@ CONFIG_PPC_FPU=y CONFIG_ALTIVEC=y CONFIG_PPC_STD_MMU=y CONFIG_SMP=y -CONFIG_NR_CPUS=2 +CONFIG_NR_CPUS=4 # # Code maturity level options @@ -53,6 +53,7 @@ CONFIG_IKCONFIG=y CONFIG_IKCONFIG_PROC=y # CONFIG_CPUSETS is not set CONFIG_INITRAMFS_SOURCE="" +CONFIG_CC_OPTIMIZE_FOR_SIZE=y # CONFIG_EMBEDDED is not set CONFIG_KALLSYMS=y # CONFIG_KALLSYMS_ALL is not set @@ -116,6 +117,8 @@ CONFIG_U3_DART=y CONFIG_MPIC=y # CONFIG_PPC_RTAS is not set # CONFIG_MMIO_NVRAM is not set +CONFIG_MPIC_BROKEN_U3=y +# CONFIG_CRASH_DUMP is not set # CONFIG_PPC_MPC106 is not set CONFIG_GENERIC_TBSYNC=y CONFIG_CPU_FREQ=y @@ -162,7 +165,7 @@ CONFIG_FLATMEM_MANUAL=y CONFIG_FLATMEM=y CONFIG_FLAT_NODE_MEM_MAP=y # CONFIG_SPARSEMEM_STATIC is not set -CONFIG_SPLIT_PTLOCK_CPUS=4096 +CONFIG_SPLIT_PTLOCK_CPUS=4 # CONFIG_PPC_64K_PAGES is not set # CONFIG_SCHED_SMT is not set CONFIG_PROC_DEVICETREE=y @@ -629,6 +632,7 @@ CONFIG_THERM_PM72=y CONFIG_WINDFARM=y CONFIG_WINDFARM_PM81=y CONFIG_WINDFARM_PM91=y +CONFIG_WINDFARM_PM112=y # # Network device support @@ -681,7 +685,7 @@ CONFIG_E1000=y # CONFIG_SIS190 is not set # CONFIG_SKGE is not set # CONFIG_SK98LIN is not set -CONFIG_TIGON3=m +CONFIG_TIGON3=y # CONFIG_BNX2 is not set # CONFIG_MV643XX_ETH is not set @@ -1141,6 +1145,7 @@ CONFIG_USB_STORAGE_JUMPSHOT=y # CONFIG_USB_HID=y CONFIG_USB_HIDINPUT=y +# CONFIG_USB_HIDINPUT_POWERBOOK is not set CONFIG_HID_FF=y CONFIG_HID_PID=y CONFIG_LOGITECH_FF=y @@ -1203,6 +1208,7 @@ CONFIG_USB_MON=y CONFIG_USB_SERIAL=m CONFIG_USB_SERIAL_GENERIC=y # CONFIG_USB_SERIAL_AIRPRIME is not set +# CONFIG_USB_SERIAL_ANYDATA is not set CONFIG_USB_SERIAL_BELKIN=m CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m # CONFIG_USB_SERIAL_CP2101 is not set @@ -1233,7 +1239,6 @@ CONFIG_USB_SERIAL_KEYSPAN_USA49WLC=y CONFIG_USB_SERIAL_KLSI=m CONFIG_USB_SERIAL_KOBIL_SCT=m CONFIG_USB_SERIAL_MCT_U232=m -# CONFIG_USB_SERIAL_NOKIA_DKU2 is not set CONFIG_USB_SERIAL_PL2303=m # CONFIG_USB_SERIAL_HP4X is not set CONFIG_USB_SERIAL_SAFE=m From arnd at arndb.de Sat Jan 7 13:02:29 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Sat, 7 Jan 2006 02:02:29 +0000 Subject: Support for Bogus Net In-Reply-To: References: Message-ID: <200601070202.29680.arnd@arndb.de> [added linuxppc64-dev to Cc:] On Friday 06 January 2006 23:49, Eric V Van hensbergen wrote: > Okay, I'll merge it up to 2.6.15, remove the bogus halt and console stuff > and send it out to this list. ?We can either merge it with the other > "stuff" (the console stuff with systemsim.h in it), or submit it after that > patch gets submitted mainline. How about if we make an extra patch for the systemsim.h file? I find it hard to tell which of the patches (console or the others) is more likely to go in soon, so maybe it works best to have patches like this: [patches from eric] systemsim-add-systemsim.h.diff systemsim-add-bogusdisk.diff systemsim-add-bogusnet.diff [patches from ryan] hvc-cleanups.diff hvc-more-changes.diff hvc-add-rtas.diff hvc-add-systemsim.diff with the hvc-add-systemsim.diff depending on systemsim-add-systemsim.h.diff to be applied first. For reference, I've added my last version of that patch this mail, so we have the same base (and can get comments on it). Arnd <>< Index: linux-2.6.15-rc/include/asm-powerpc/systemsim.h =================================================================== --- /dev/null +++ linux-2.6.15-rc/include/asm-powerpc/systemsim.h @@ -0,0 +1,132 @@ +/* + * + * Copyright (C) 2001, 2005 IBM + * + * Filename : systemsim.h + * + * Originator : Patrick Bohrer and Charles Lefurgy + * Modified By : Eric Van Hensbegren + * + * Purpose : + * + * This file is compiled with programs that are run under the + * PowerPC Full System simulator. For example, stand-alone programs + * or operating systems. The programs call the callthru wrapper + * functions which use an illegal PowerPC instruction to signal the + * simulator to emulate special support. + * + */ + +#ifndef _SYSTEMSIM_CONFIG_H_ +#define _SYSTEMSIM_CONFIG_H_ +#ifdef __KERNEL__ + +/* The functions callthru0 to callthru5 setup up the arguments for the + * simulator callthru and then use the callthru instruction. Note that + * 0-5 specify the number of arguments after the command */ + +/* Note: Arguments are cast as void* to prevent casting by the + compiler. This way, you can pass pointers, integers, etc. in + machine register and have the simulator interpret what the + register is supposed to be. To help with typing errors when using + callthrus, we provide wrapper functions for each callthru. The + wrappers cast all arguments to void*. Unfortunately, this results + in a lot of compiler warnings that I do not know how to remove. If + you modify this code, be aware that we are trying to pick a type + that is the size of the registers (32-bit or 64-bit) and that is + why are choosing to cast to a void* (it should be the size of a + machine register) */ + +static inline int callthru0(int command) +{ + register int c asm("r3") = command; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c)); + return (c); +} +static inline int callthru1(int command, unsigned long arg1) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1)); + return c; +} +static inline int callthru2(int command, unsigned long arg1, unsigned long arg2) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2)); + return c; +} +static inline int callthru3(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3)); + return c; +} +static inline int callthru4(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + register unsigned long a4 asm("r7") = arg4; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3), "r"(a4)); + return c; +} +static inline int callthru5(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + register unsigned long a4 asm("r7") = arg4; + register unsigned long a5 asm("r8") = arg5; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3), "r"(a4), "r"(a5)); + return c; +} +static inline int callthru6(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5, unsigned long arg6) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + register unsigned long a4 asm("r7") = arg4; + register unsigned long a5 asm("r8") = arg5; + register unsigned long a6 asm("r9") = arg6; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3), "r"(a4), "r"(a5), "r"(a6)); + return c; +} +static inline int callthru7(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5, unsigned long arg6, + unsigned long arg7) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + register unsigned long a4 asm("r7") = arg4; + register unsigned long a5 asm("r8") = arg5; + register unsigned long a6 asm("r9") = arg6; + register unsigned long a7 asm("r10") = arg7; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7)); + return c; +} + +#endif /* __KERNEL__ */ +#endif/* _SYSTEMSIM_CONFIG_H_ */ From arnd at arndb.de Sat Jan 7 14:56:59 2006 From: arnd at arndb.de (Arnd Bergmann) Date: Sat, 7 Jan 2006 04:56:59 +0100 Subject: Support for Bogus Net In-Reply-To: References: Message-ID: <200601070456.59887.arnd@arndb.de> Am Samstag, 7. Januar 2006 04:20 schrieb Mike Kistler: > Regarding the systemsim-add-systemsim.h patch, I notice that the XLC forms > (which are available in callthru_config.h) are not in the proposed > systemsim.h file. ?It would be nice to have these, protected by the > appropriate ifdefs of course, to enable application development with XLC. > Otherwise, we'll need to continue to ship callthru_config.h with Mambo and > point users to this if they want to use XLC. Would that be for compiling the kernel with XLC or for user space applications running in the simulated linux environment? If I understand you correctly that this is about applications using the callthru inlines, then they should use a differrent header file anyway. Note that the version I posted has everything wrapped inside '#ifdef __KERNEL__', so user applications can not use it. That is absolutely intentional, as user applications should never directly include kernel header files anyway. The correct solution would be to either include a copy of the header file in the application using it or to provide a separate package that installs a header file for user space into $PREFIX/include. Shipping the file with mambo should be ok as well, but I would suggest to make a clear separation of header files for use on the host side (the one running the simulator binary) and header files for the target side. Arnd <>< From mkistler at us.ibm.com Sat Jan 7 14:20:04 2006 From: mkistler at us.ibm.com (Mike Kistler) Date: Fri, 6 Jan 2006 21:20:04 -0600 Subject: Support for Bogus Net In-Reply-To: <200601070202.29680.arnd@arndb.de> Message-ID: I like this approach because it seems like we should be able to quickly finalize and publish the systemsim-add-bogusnet patch (which is currently my main focus). Regarding the systemsim-add-systemsim.h patch, I notice that the XLC forms (which are available in callthru_config.h) are not in the proposed systemsim.h file. It would be nice to have these, protected by the appropriate ifdefs of course, to enable application development with XLC. Otherwise, we'll need to continue to ship callthru_config.h with Mambo and point users to this if they want to use XLC. Regards, Mike Arnd Bergmann 01/06/2006 08:02 PM To: Eric V Van hensbergen/Austin/IBM at IBMUS cc: David Woodhouse , Mike Kistler/Austin/IBM at IBMUS, Patrick Bohrer/Austin/IBM at IBMUS, rsa at us.ltcfwd.linux.ibm.com, linuxppc64-dev at ozlabs.org, ericvh at gmail.com Subject: Re: Support for Bogus Net [added linuxppc64-dev to Cc:] On Friday 06 January 2006 23:49, Eric V Van hensbergen wrote: > Okay, I'll merge it up to 2.6.15, remove the bogus halt and console stuff > and send it out to this list. We can either merge it with the other > "stuff" (the console stuff with systemsim.h in it), or submit it after that > patch gets submitted mainline. How about if we make an extra patch for the systemsim.h file? I find it hard to tell which of the patches (console or the others) is more likely to go in soon, so maybe it works best to have patches like this: [patches from eric] systemsim-add-systemsim.h.diff systemsim-add-bogusdisk.diff systemsim-add-bogusnet.diff [patches from ryan] hvc-cleanups.diff hvc-more-changes.diff hvc-add-rtas.diff hvc-add-systemsim.diff with the hvc-add-systemsim.diff depending on systemsim-add-systemsim.h.diff to be applied first. For reference, I've added my last version of that patch this mail, so we have the same base (and can get comments on it). Arnd <>< Index: linux-2.6.15-rc/include/asm-powerpc/systemsim.h =================================================================== --- /dev/null +++ linux-2.6.15-rc/include/asm-powerpc/systemsim.h @@ -0,0 +1,132 @@ +/* + * + * Copyright (C) 2001, 2005 IBM + * + * Filename : systemsim.h + * + * Originator : Patrick Bohrer and Charles Lefurgy + * Modified By : Eric Van Hensbegren + * + * Purpose : + * + * This file is compiled with programs that are run under the + * PowerPC Full System simulator. For example, stand-alone programs + * or operating systems. The programs call the callthru wrapper + * functions which use an illegal PowerPC instruction to signal the + * simulator to emulate special support. + * + */ + +#ifndef _SYSTEMSIM_CONFIG_H_ +#define _SYSTEMSIM_CONFIG_H_ +#ifdef __KERNEL__ + +/* The functions callthru0 to callthru5 setup up the arguments for the + * simulator callthru and then use the callthru instruction. Note that + * 0-5 specify the number of arguments after the command */ + +/* Note: Arguments are cast as void* to prevent casting by the + compiler. This way, you can pass pointers, integers, etc. in + machine register and have the simulator interpret what the + register is supposed to be. To help with typing errors when using + callthrus, we provide wrapper functions for each callthru. The + wrappers cast all arguments to void*. Unfortunately, this results + in a lot of compiler warnings that I do not know how to remove. If + you modify this code, be aware that we are trying to pick a type + that is the size of the registers (32-bit or 64-bit) and that is + why are choosing to cast to a void* (it should be the size of a + machine register) */ + +static inline int callthru0(int command) +{ + register int c asm("r3") = command; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c)); + return (c); +} +static inline int callthru1(int command, unsigned long arg1) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1)); + return c; +} +static inline int callthru2(int command, unsigned long arg1, unsigned long arg2) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2)); + return c; +} +static inline int callthru3(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3)); + return c; +} +static inline int callthru4(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + register unsigned long a4 asm("r7") = arg4; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3), "r"(a4)); + return c; +} +static inline int callthru5(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + register unsigned long a4 asm("r7") = arg4; + register unsigned long a5 asm("r8") = arg5; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3), "r"(a4), "r"(a5)); + return c; +} +static inline int callthru6(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5, unsigned long arg6) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + register unsigned long a4 asm("r7") = arg4; + register unsigned long a5 asm("r8") = arg5; + register unsigned long a6 asm("r9") = arg6; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3), "r"(a4), "r"(a5), "r"(a6)); + return c; +} +static inline int callthru7(int command, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5, unsigned long arg6, + unsigned long arg7) +{ + register int c asm("r3") = command; + register unsigned long a1 asm("r4") = arg1; + register unsigned long a2 asm("r5") = arg2; + register unsigned long a3 asm("r6") = arg3; + register unsigned long a4 asm("r7") = arg4; + register unsigned long a5 asm("r8") = arg5; + register unsigned long a6 asm("r9") = arg6; + register unsigned long a7 asm("r10") = arg7; + asm volatile (".long 0x000eaeb0":"=r" (c):"r"(c), "r"(a1), "r"(a2), + "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7)); + return c; +} + +#endif /* __KERNEL__ */ +#endif/* _SYSTEMSIM_CONFIG_H_ */ -------------- next part -------------- An HTML attachment was scrubbed... URL: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060106/d0befdf0/attachment.htm From clameter at engr.sgi.com Sat Jan 7 17:35:08 2006 From: clameter at engr.sgi.com (Christoph Lameter) Date: Fri, 6 Jan 2006 22:35:08 -0800 (PST) Subject: [ANNOUNCE] numactl 0.9 released In-Reply-To: <200601062210.31154.ak@suse.de> References: <200601032257.53039.ak@suse.de> <200601062210.31154.ak@suse.de> Message-ID: On Fri, 6 Jan 2006, Andi Kleen wrote: > Did migrate_pages make it into Linus' tree yet? I don't see it there yet. Not yet but I would expect it for 2.6.16. There is also an open suse RFE for inclusion of migrate_pages into SLES10. > I think the name of the command is a bit unintuitive - how > about calling it changenodemem or movenodemem ? (better suggestions > welcome) The name is a bit unintuitive but its named the same way as the kernel function. It moves pages of a process so I guess the name is good enough unless someone comes up with a better name. Here is a patch to fix the various issues you pointed out. Thank you: Index: numactl-0.9/migratepages.8 =================================================================== --- numactl-0.9.orig/migratepages.8 2006-01-06 12:18:03.000000000 -0800 +++ numactl-0.9/migratepages.8 2006-01-06 22:29:25.000000000 -0800 @@ -11,10 +11,16 @@ migratepages \- Migrate the physical loc pid from-nodes to-nodes .SH DESCRIPTION .B migratepages -moves the physical localtion of a processes pages without any changes of the +moves the physical location of a processes pages without any changes of the virtual address space of the process. This is usually done to optimize the performance of a process by moving the pages near to the processor executing a process. + +If multiple nodes are specified for from-nodes or to-nodes then +an attempt is made to preserve the relative location of +each page in each nodeset. F.e. if we move from nodes 2-5 to +7,9,12-13 then the preferred mode of transfer is to move pages from +2->7, 3->9, 4->12 and 5->13. .TP Valid node specifiers .TS @@ -61,3 +67,6 @@ migratepages is under the GNU General Pu .I ftok(3) , .I shmat(2) +, +.I taskset(1) + Index: numactl-0.9/migratepages.c =================================================================== --- numactl-0.9.orig/migratepages.c 2006-01-06 22:29:44.000000000 -0800 +++ numactl-0.9/migratepages.c 2006-01-06 22:29:47.000000000 -0800 @@ -81,7 +81,7 @@ int main(int argc, char *argv[]) checknuma(); pid = strtoul(argv[1], &end, 0); - if (*end) + if (*end || end == argv[1]) usage(); fromnodes = nodemask(argv[2]); From mkistler at us.ibm.com Sun Jan 8 01:07:49 2006 From: mkistler at us.ibm.com (Mike Kistler) Date: Sat, 7 Jan 2006 08:07:49 -0600 Subject: Support for Bogus Net In-Reply-To: <200601070456.59887.arnd@arndb.de> Message-ID: Thanks Arnd .... I understand now. This looks good to me. Regards, Mike Arnd Bergmann 01/06/2006 09:56 PM To: Mike Kistler/Austin/IBM at IBMUS cc: David Woodhouse , Eric V Van hensbergen/Austin/IBM at IBMUS, ericvh at gmail.com, linuxppc64-dev at ozlabs.org, Patrick Bohrer/Austin/IBM at IBMUS, rsa at us.ltcfwd.linux.ibm.com Subject: Re: Support for Bogus Net Am Samstag, 7. Januar 2006 04:20 schrieb Mike Kistler: > Regarding the systemsim-add-systemsim.h patch, I notice that the XLC forms > (which are available in callthru_config.h) are not in the proposed > systemsim.h file. It would be nice to have these, protected by the > appropriate ifdefs of course, to enable application development with XLC. > Otherwise, we'll need to continue to ship callthru_config.h with Mambo and > point users to this if they want to use XLC. Would that be for compiling the kernel with XLC or for user space applications running in the simulated linux environment? If I understand you correctly that this is about applications using the callthru inlines, then they should use a differrent header file anyway. Note that the version I posted has everything wrapped inside '#ifdef __KERNEL__', so user applications can not use it. That is absolutely intentional, as user applications should never directly include kernel header files anyway. The correct solution would be to either include a copy of the header file in the application using it or to provide a separate package that installs a header file for user space into $PREFIX/include. Shipping the file with mambo should be ok as well, but I would suggest to make a clear separation of header files for use on the host side (the one running the simulator binary) and header files for the target side. Arnd <>< -------------- next part -------------- An HTML attachment was scrubbed... URL: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060107/0f6d4c13/attachment.htm From schwab at suse.de Sun Jan 8 01:34:33 2006 From: schwab at suse.de (Andreas Schwab) Date: Sat, 07 Jan 2006 15:34:33 +0100 Subject: [PATCH] 0/5 powerpc: Platform & i2c updates & cpufreq In-Reply-To: <1136593470.4840.154.camel@localhost.localdomain> (Benjamin Herrenschmidt's message of "Sat, 07 Jan 2006 11:24:30 +1100") References: <1136593470.4840.154.camel@localhost.localdomain> Message-ID: Benjamin Herrenschmidt writes: > This serie of patch replaces the 2 I posted recently for platform > functions & g5 cpufreq. It's not a 100% finished job yet but it's > getting closer. The patch appears to break ALSA on PowerMac Dual 1.8GHz. The volume control is going wild, and I'm getting distorted sound. Andreas. -- Andreas Schwab, SuSE Labs, schwab at suse.de SuSE Linux Products GmbH, Maxfeldstra?e 5, 90409 N?rnberg, Germany PGP key fingerprint = 58CA 54C7 6D53 942B 1756 01D3 44D5 214B 8276 4ED5 "And now for something completely different." From jschopp at austin.ibm.com Sun Jan 8 04:49:17 2006 From: jschopp at austin.ibm.com (Joel Schopp) Date: Sat, 07 Jan 2006 11:49:17 -0600 Subject: PowerPC fastpaths for mutex subsystem In-Reply-To: <20060106002919.GA29190@pb15.lixom.net> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> <20060106002919.GA29190@pb15.lixom.net> Message-ID: <43BFFF1D.7030007@austin.ibm.com> This is the second pass at optimizing the fastpath for the new mutex subsystem on PowerPC. I think it is ready to be included in the series with the other mutex patches now. Tested on a 4 core (2 SMT threads/core) Power5 machine with gcc 3.3.2. Test results from synchro-test.ko: All tests run for default 5 seconds Threads semaphores mutexes mutexes+attached 1 63,465,364 58,404,630 62,109,571 4 58,424,282 35,541,297 37,820,794 8 40,731,668 35,541,297 40,281,768 16 38,372,769 37,256,298 41,751,764 32 38,406,895 36,933,675 38,731,571 64 37,232,017 36,222,480 40,766,379 Signed-off-by: Joel Schopp -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: powerpcmutex.patch Url: http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20060107/510b7134/attachment.txt From olh at suse.de Sun Jan 8 08:28:51 2006 From: olh at suse.de (Olaf Hering) Date: Sat, 7 Jan 2006 22:28:51 +0100 Subject: [PATCH 14/22] ppc64: RPA PHP to EEH code movement In-Reply-To: <20051006234624.GO29826@austin.ibm.com> References: <20051006232032.GA29826@austin.ibm.com> <20051006234624.GO29826@austin.ibm.com> Message-ID: <20060107212851.GA31731@suse.de> On Thu, Oct 06, Linas Vepstas wrote: > > 14-rpaphp-migrate.patch > > This patch moves some pci device add & remove code from the PCI > hotplug directory to the arch/ppc64/kernel directory, and cleans > it up a tad. The primary reason for this is that the code performs > some fairly generic operations that are shared with the PCI error > recovery code (living in the arch/ppc64/kernel directory). > +++ linux-2.6.14-rc2-git6/arch/ppc64/kernel/pci_dlpar.c 2005-10-06 17:54:00.306445890 -0500 > +pcibios_add_pci_devices(struct pci_bus * bus) > + eeh_add_device_tree_early(dn); eeh_add_device_tree_early is in eeh.c, which depends on CONFIG_EEH. but pci_dlpar.c is compiled unconditionally. Current powerpc.git gives: arch/powerpc/platforms/built-in.o(.text+0x99b8): In function `.pcibios_add_pci_devices': : undefined reference to `.eeh_add_device_tree_early' arch/powerpc/platforms/built-in.o(.text+0x9b40): In function `.pcibios_remove_pci_devices': : undefined reference to `.eeh_remove_bus_device' -- short story of a lazy sysadmin: alias appserv=wotan From benh at kernel.crashing.org Sun Jan 8 09:04:53 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sun, 08 Jan 2006 09:04:53 +1100 Subject: [PATCH] 0/5 powerpc: Platform & i2c updates & cpufreq In-Reply-To: References: <1136593470.4840.154.camel@localhost.localdomain> Message-ID: <1136671493.30123.2.camel@localhost.localdomain> On Sat, 2006-01-07 at 15:34 +0100, Andreas Schwab wrote: > Benjamin Herrenschmidt writes: > > > This serie of patch replaces the 2 I posted recently for platform > > functions & g5 cpufreq. It's not a 100% finished job yet but it's > > getting closer. > > The patch appears to break ALSA on PowerMac Dual 1.8GHz. The volume > control is going wild, and I'm getting distorted sound. Which patch specifically ? I suspect a problem with the keywest i2c code, I 'll have a look today or tomorrow. Ben. From benh at kernel.crashing.org Sun Jan 8 09:09:01 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sun, 08 Jan 2006 09:09:01 +1100 Subject: [PATCH] 0/5 powerpc: Platform & i2c updates & cpufreq In-Reply-To: References: <1136593470.4840.154.camel@localhost.localdomain> Message-ID: <1136671741.30123.6.camel@localhost.localdomain> On Sat, 2006-01-07 at 15:34 +0100, Andreas Schwab wrote: > Benjamin Herrenschmidt writes: > > > This serie of patch replaces the 2 I posted recently for platform > > functions & g5 cpufreq. It's not a 100% finished job yet but it's > > getting closer. > > The patch appears to break ALSA on PowerMac Dual 1.8GHz. The volume > control is going wild, and I'm getting distorted sound. Ok, I think it's the sound driver's i2c code relied on a bug in i2c-keywest that has been fixed by the new i2c driver. I'll post a fix later today. Ben. From akpm at osdl.org Sun Jan 8 09:37:22 2006 From: akpm at osdl.org (Andrew Morton) Date: Sat, 7 Jan 2006 14:37:22 -0800 Subject: PowerPC fastpaths for mutex subsystem In-Reply-To: <43BFFF1D.7030007@austin.ibm.com> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> <20060106002919.GA29190@pb15.lixom.net> <43BFFF1D.7030007@austin.ibm.com> Message-ID: <20060107143722.25afd85d.akpm@osdl.org> Joel Schopp wrote: > > This is the second pass at optimizing the fastpath for the new mutex subsystem > on PowerPC. I think it is ready to be included in the series with the other > mutex patches now. Tested on a 4 core (2 SMT threads/core) Power5 machine with > gcc 3.3.2. > > Test results from synchro-test.ko: > > All tests run for default 5 seconds > Threads semaphores mutexes mutexes+attached > 1 63,465,364 58,404,630 62,109,571 > 4 58,424,282 35,541,297 37,820,794 > 8 40,731,668 35,541,297 40,281,768 > 16 38,372,769 37,256,298 41,751,764 > 32 38,406,895 36,933,675 38,731,571 > 64 37,232,017 36,222,480 40,766,379 > Doens't this mean that the sped-up mutexes are still slower than semaphores? From benh at kernel.crashing.org Sun Jan 8 15:52:36 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sun, 08 Jan 2006 15:52:36 +1100 Subject: [PATCH] powerpc: Fix PowerMac sound i2c Message-ID: <1136695956.30123.44.camel@localhost.localdomain> My patch reworking the PowerMac i2c code break the sound drivers as they used to rely on some broken behaviour of i2c-keywest that is gone now. This patch should fix them (tested on a g5 with alsa only). It might also fix an oops if the alsa driver hits an unsupported chip. Signed-off-by: Benjamin Herrenschmidt Index: linux-work/sound/ppc/tumbler.c =================================================================== --- linux-work.orig/sound/ppc/tumbler.c 2005-11-24 17:19:14.000000000 +1100 +++ linux-work/sound/ppc/tumbler.c 2006-01-08 15:18:09.000000000 +1100 @@ -137,6 +137,22 @@ static int send_init_client(pmac_keywest return 0; } +static int tumbler_write_block(struct i2c_client *client, u8 reg, int len, + u8 *values) +{ + union i2c_smbus_data data; + int err; + + data.block[0] = len; + memcpy(&data.block[1], values, len); + err = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_WRITE, reg, I2C_SMBUS_I2C_BLOCK_DATA, + &data); + return err; +} + + + static int tumbler_init_client(pmac_keywest_t *i2c) { @@ -239,8 +255,7 @@ static int tumbler_set_master_volume(pma block[4] = (right_vol >> 8) & 0xff; block[5] = (right_vol >> 0) & 0xff; - if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_VOL, - 6, block) < 0) { + if (tumbler_write_block(mix->i2c.client, TAS_REG_VOL, 6, block) < 0) { snd_printk("failed to set volume \n"); return -EINVAL; } @@ -340,8 +355,7 @@ static int tumbler_set_drc(pmac_tumbler_ val[1] = 0; } - if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_DRC, - 2, val) < 0) { + if (tumbler_write_block(mix->i2c.client, TAS_REG_DRC, 2, val) < 0) { snd_printk("failed to set DRC\n"); return -EINVAL; } @@ -376,8 +390,7 @@ static int snapper_set_drc(pmac_tumbler_ val[4] = 0x60; val[5] = 0xa0; - if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_DRC, - 6, val) < 0) { + if (tumbler_write_block(mix->i2c.client, TAS_REG_DRC, 6, val) < 0) { snd_printk("failed to set DRC\n"); return -EINVAL; } @@ -481,8 +494,8 @@ static int tumbler_set_mono_volume(pmac_ vol = info->table[vol]; for (i = 0; i < info->bytes; i++) block[i] = (vol >> ((info->bytes - i - 1) * 8)) & 0xff; - if (i2c_smbus_write_block_data(mix->i2c.client, info->reg, - info->bytes, block) < 0) { + if (tumbler_write_block(mix->i2c.client, info->reg, + info->bytes, block) < 0) { snd_printk("failed to set mono volume %d\n", info->index); return -EINVAL; } @@ -611,7 +624,7 @@ static int snapper_set_mix_vol1(pmac_tum for (j = 0; j < 3; j++) block[i * 3 + j] = (vol >> ((2 - j) * 8)) & 0xff; } - if (i2c_smbus_write_block_data(mix->i2c.client, reg, 9, block) < 0) { + if (tumbler_write_block(mix->i2c.client, reg, 9, block) < 0) { snd_printk("failed to set mono volume %d\n", reg); return -EINVAL; } Index: linux-work/sound/oss/dmasound/tas_common.h =================================================================== --- linux-work.orig/sound/oss/dmasound/tas_common.h 2005-11-24 17:19:14.000000000 +1100 +++ linux-work/sound/oss/dmasound/tas_common.h 2006-01-08 15:33:29.000000000 +1100 @@ -157,6 +157,21 @@ tas_mono_to_stereo(uint mono) return mono | (mono<<8); } +static int tas_write_block(struct i2c_client *client, u8 reg, int len, u8 *vals) +{ + union i2c_smbus_data data; + int err; + + data.block[0] = len; + memcpy(&data.block[1], vals, len); + err = i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_WRITE, reg, I2C_SMBUS_I2C_BLOCK_DATA, + &data); + return err; +} + + + /* * Todo: make these functions a bit more efficient ! */ @@ -178,10 +193,8 @@ tas_write_register( struct tas_data_t *s if (write_mode & WRITE_SHADOW) memcpy(self->shadow[reg_num],data,reg_width); if (write_mode & WRITE_HW) { - rc=i2c_smbus_write_block_data(self->client, - reg_num, - reg_width, - data); + rc = tas_write_block(self->client, reg_num, + reg_width, data); if (rc < 0) { printk("tas: I2C block write failed \n"); return rc; @@ -199,10 +212,8 @@ tas_sync_register( struct tas_data_t *se if (reg_width==0 || self==NULL) return -EINVAL; - rc=i2c_smbus_write_block_data(self->client, - reg_num, - reg_width, - self->shadow[reg_num]); + rc = tas_write_block(self->client, reg_num, + reg_width, self->shadow[reg_num]); if (rc < 0) { printk("tas: I2C block write failed \n"); return rc; Index: linux-work/sound/ppc/pmac.c =================================================================== --- linux-work.orig/sound/ppc/pmac.c 2005-12-19 16:13:48.000000000 +1100 +++ linux-work/sound/ppc/pmac.c 2006-01-08 15:37:10.000000000 +1100 @@ -74,7 +74,7 @@ static int snd_pmac_dbdma_alloc(pmac_t * static void snd_pmac_dbdma_free(pmac_t *chip, pmac_dbdma_t *rec) { - if (rec) { + if (rec->space) { unsigned int rsize = sizeof(struct dbdma_cmd) * (rec->size + 1); dma_free_coherent(&chip->pdev->dev, rsize, rec->space, rec->dma_base); @@ -895,6 +895,7 @@ static int __init snd_pmac_detect(pmac_t chip->can_capture = 1; chip->num_freqs = ARRAY_SIZE(awacs_freqs); chip->freq_table = awacs_freqs; + chip->pdev = NULL; chip->control_mask = MASK_IEPC | MASK_IEE | 0x11; /* default */ From hollis at penguinppc.org Sun Jan 8 15:55:48 2006 From: hollis at penguinppc.org (Hollis Blanchard) Date: Sat, 7 Jan 2006 22:55:48 -0600 Subject: [PATCH] powerpc: Fix PowerMac sound i2c In-Reply-To: <1136695956.30123.44.camel@localhost.localdomain> References: <1136695956.30123.44.camel@localhost.localdomain> Message-ID: On Jan 7, 2006, at 10:52 PM, Benjamin Herrenschmidt wrote: > + data.block[0] = len; > + memcpy(&data.block[1], values, len); Seem to be mixing tabs and spaces here (in both *_write_block functions). -Hollis From benh at kernel.crashing.org Sun Jan 8 17:22:59 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Sun, 08 Jan 2006 17:22:59 +1100 Subject: [PATCH] powerpc: Fix PowerMac sound i2c In-Reply-To: References: <1136695956.30123.44.camel@localhost.localdomain> Message-ID: <1136701380.30123.46.camel@localhost.localdomain> On Sat, 2006-01-07 at 22:55 -0600, Hollis Blanchard wrote: > On Jan 7, 2006, at 10:52 PM, Benjamin Herrenschmidt wrote: > > > + data.block[0] = len; > > + memcpy(&data.block[1], values, len); > > Seem to be mixing tabs and spaces here (in both *_write_block > functions). You mean the patch got mangled or the code is mixing tab/spaces ? I think the driver was pretty mixed up in the first place, I'll have to check. Ben. From anton at samba.org Sun Jan 8 18:43:57 2006 From: anton at samba.org (Anton Blanchard) Date: Sun, 8 Jan 2006 18:43:57 +1100 Subject: PowerPC fastpaths for mutex subsystem In-Reply-To: <20060107143722.25afd85d.akpm@osdl.org> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> <20060106002919.GA29190@pb15.lixom.net> <43BFFF1D.7030007@austin.ibm.com> <20060107143722.25afd85d.akpm@osdl.org> Message-ID: <20060108074356.GM26499@krispykreme> > Doens't this mean that the sped-up mutexes are still slower than semaphores? Wasnt most of the x86 mutex gain a result of going from fair to unfair operation? The current ppc64 semaphores are unfair. Anton From akpm at osdl.org Sun Jan 8 19:00:21 2006 From: akpm at osdl.org (Andrew Morton) Date: Sun, 8 Jan 2006 00:00:21 -0800 Subject: PowerPC fastpaths for mutex subsystem In-Reply-To: <20060108074356.GM26499@krispykreme> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> <20060106002919.GA29190@pb15.lixom.net> <43BFFF1D.7030007@austin.ibm.com> <20060107143722.25afd85d.akpm@osdl.org> <20060108074356.GM26499@krispykreme> Message-ID: <20060108000021.588c6f5f.akpm@osdl.org> Anton Blanchard wrote: > > > > Doens't this mean that the sped-up mutexes are still slower than semaphores? > > Wasnt most of the x86 mutex gain a result of going from fair to unfair > operation? The current ppc64 semaphores are unfair. > What's "unfair"? Mutexes are FIFO, as are x86 semaphores. From anton at samba.org Sun Jan 8 19:23:01 2006 From: anton at samba.org (Anton Blanchard) Date: Sun, 8 Jan 2006 19:23:01 +1100 Subject: PowerPC fastpaths for mutex subsystem In-Reply-To: <20060108000021.588c6f5f.akpm@osdl.org> References: <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> <20060106002919.GA29190@pb15.lixom.net> <43BFFF1D.7030007@austin.ibm.com> <20060107143722.25afd85d.akpm@osdl.org> <20060108074356.GM26499@krispykreme> <20060108000021.588c6f5f.akpm@osdl.org> Message-ID: <20060108082301.GN26499@krispykreme> > What's "unfair"? Mutexes are FIFO, as are x86 semaphores. The ppc64 semaphores dont force everyone into the slow path under contention. So you could drop and pick up the semaphore even with someone waiting. I thought thats how the new mutex code worked. Anton From mingo at elte.hu Sun Jan 8 20:48:39 2006 From: mingo at elte.hu (Ingo Molnar) Date: Sun, 8 Jan 2006 10:48:39 +0100 Subject: PowerPC fastpaths for mutex subsystem In-Reply-To: <43BFFF1D.7030007@austin.ibm.com> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> <20060106002919.GA29190@pb15.lixom.net> <43BFFF1D.7030007@austin.ibm.com> Message-ID: <20060108094839.GA16887@elte.hu> * Joel Schopp wrote: > Tested on a 4 core (2 SMT threads/core) Power5 machine with gcc 3.3.2. > Test results from synchro-test.ko: > > All tests run for default 5 seconds > Threads semaphores mutexes mutexes+attached > 1 63,465,364 58,404,630 62,109,571 > 4 58,424,282 35,541,297 37,820,794 > 8 40,731,668 35,541,297 40,281,768 > 16 38,372,769 37,256,298 41,751,764 > 32 38,406,895 36,933,675 38,731,571 > 64 37,232,017 36,222,480 40,766,379 interesting. Could you try two things? Firstly, could you add some minimal delays to the lock/unlock path, of at least 1 usec? E.g. "synchro-test.ko load=1 interval=1". [but you could try longer delays too, 10 usecs is still realistic.] secondly, could you try the VFS creat+unlink test via the test-mutex.c code below, with something like: ./test-mutex V 16 10 (this tests with 16 tasks, for 10 seconds.) You'll get a useful ops/sec number out of this test, but the other stats will only be calculated if you implement the rdtsc() macro to read cycles - right now it defaults to 'always 0' on ppc, i386 and ia64 has it implemented. Also, beware that the default atomic_inc()/dec() is unsafe (only i386 and ia64 has the real thing implemented), you might want to add a safe PPC implementation. thirdly, could you run 'vmstat 1' during the tests, and post those lines too? Here i'm curious about two things: the average runqueue length (whether we have overscheduling), and CPU utilization and idle time left (how efficiently cycles are preserved in contention). [btw., does ppc have an idle=poll equivalent mode of idling?] also, there seems to be some fluctuation in the numbers - could you try to run a few more to see how stable the numbers are? Ingo ------------ /* * Copyright (C) 2005, Ingo Molnar */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __ia64__ #include #include "mmtimer.h" int mmtimer_fd; unsigned long __mm_timer_clock_res; unsigned long *__mm_clock_dev; unsigned long __mm_clock_offset; #endif unsigned long *shared; #define mutex_lock() gettimeofday((void *)0, (void *)10) #define mutex_unlock() gettimeofday((void *)0, (void *)20) #define down() gettimeofday((void *)0, (void *)100) #define up() gettimeofday((void *)0, (void *)200) #define down_write() gettimeofday((void *)0, (void *)1000) #define up_write() gettimeofday((void *)0, (void *)2000) #define down_read() gettimeofday((void *)0, (void *)10000) #define up_read() gettimeofday((void *)0, (void *)20000) /* * Shared locks and variables between the test tasks: */ #define CACHELINE_SIZE (128/sizeof(long)) enum { SHARED_DELTA_SUM = 0*CACHELINE_SIZE, SHARED_DELTA_MAX = 1*CACHELINE_SIZE, SHARED_DELTA2_SUM = 2*CACHELINE_SIZE, SHARED_DELTA2_MAX = 3*CACHELINE_SIZE, SHARED_DELTA3_SUM = 4*CACHELINE_SIZE, SHARED_DELTA3_MAX = 5*CACHELINE_SIZE, SHARED_DELTA_DELTA_SUM = 6*CACHELINE_SIZE, SHARED_COUNT = 7*CACHELINE_SIZE, SHARED_SUM = 8*CACHELINE_SIZE, SHARED_LOCK = 9*CACHELINE_SIZE, SHARED_END = 10*CACHELINE_SIZE, }; #define SHARED(x) (*(shared + SHARED_##x)) #define SHARED_LL(x) (*(unsigned long long *)(shared + SHARED_##x)) #define BUG_ON(c) assert(!(c)) static unsigned long *setup_shared_var(void) { char zerobuff [4096] = { 0, }; int ret, fd; unsigned long *buf; char tmpfile[100]; sprintf(tmpfile, ".tmp_mmap-%d", getpid()); fd = creat(tmpfile, 0700); BUG_ON(fd == -1); close(fd); fd = open(tmpfile, O_RDWR|O_CREAT|O_TRUNC); unlink(tmpfile); BUG_ON(fd == -1); ret = write(fd, zerobuff, 4096); BUG_ON(ret != 4096); buf = (void *)mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); BUG_ON(buf == (void *)-1); close(fd); return buf; } #define LOOPS 10000 #ifdef __ia64__ static int setup_mmtimer(void) { unsigned long regoff; int fd, _t; size_t pagesize; if ((fd = open ("/dev/mmtimer", O_RDONLY)) == -1) perror("missing /dev/mmtimer"); else { pagesize = getpagesize(); __mm_clock_dev = mmap(0, pagesize, PROT_READ, MAP_SHARED, fd, 0); if (__mm_clock_dev != MAP_FAILED) { regoff = ioctl(fd, MMTIMER_GETOFFSET, 0); if (regoff >= 0) { __mm_clock_dev += regoff; __mm_clock_offset = *__mm_clock_dev; } else perror("reg offset ioctl failed"); _t = ioctl(fd, MMTIMER_GETFREQ, &__mm_timer_clock_res); if (_t) perror("get freq ioctl fail"); } } } #define ia64_fetchadd8_rel(p, inc) \ ({ \ __u64 ia64_intri_res; \ asm volatile ("fetchadd8.rel %0=[%1],%2" \ : "=r"(ia64_intri_res) : "r"(p), "i" (inc) \ : "memory"); \ \ ia64_intri_res; \ }) static inline void atomic_inc(unsigned long *flag) { ia64_fetchadd8_rel(flag, 1); } static inline void atomic_dec(unsigned long *flag) { ia64_fetchadd8_rel(flag, -1); } #elif defined(__i386__) static inline void atomic_inc(unsigned long *flag) { __asm__ __volatile__( "lock; incl %0\n" : "=g"(*flag) : : "memory"); } static inline void atomic_dec(unsigned long *flag) { __asm__ __volatile__( "lock; decl %0\n" : "=g"(*flag) : : "memory"); } #else static inline void atomic_inc(unsigned long *flag) { ++*flag; } static inline void atomic_dec(unsigned long *flag) { --*flag; } #endif static void LOCK(unsigned long *shared) { for (;;) { atomic_inc(&SHARED(LOCK)); if (SHARED(LOCK) == 1) break; atomic_dec(&SHARED(LOCK)); usleep(1); } } static void UNLOCK(unsigned long *shared) { atomic_dec(&SHARED(LOCK)); } static void sigint(int sig) { atomic_inc(&SHARED(END)); } static void print_status(unsigned long *shared) { unsigned long count; count = SHARED(COUNT); SHARED(COUNT) = 0; SHARED_LL(SUM) += count; printf("\r| loops/sec: %ld \r", count); fflush(stdout); } enum { TYPE_MUTEX, TYPE_SEM, TYPE_RSEM, TYPE_WSEM, TYPE_VFS, NR_TYPES }; const char * type_names[NR_TYPES] = { "Mutex", "Semaphore", "RW-semaphore Read", "RW-semaphore Write", "VFS" }; typedef unsigned long long cycles_t; typedef unsigned long long usecs_t; #ifdef __ia64__ # define rdtscll(val) \ do { \ val = *__mm_clock_dev; \ } while (0) #elif defined(__i386__) # define rdtscll(val) \ do { \ __asm__ __volatile__("rdtsc" : "=A" (val)); \ } while (0) #else # define rdtscll(val) \ do { (val) = 0LL; } while (0) #endif #define rdtod(val) \ do { \ struct timeval tv; \ \ gettimeofday(&tv, NULL); \ (val) = tv.tv_sec * 1000000ULL + tv.tv_usec; \ } while (0) #define max(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x > _y ? _x : _y; }) #define unlikely(x) __builtin_expect(!!(x), 0) int main(int argc, char **argv) { int i, parent, me, first = 1; unsigned long cpus, tasks, seconds = 0; cycles_t t0, t01, t1, delta, delta2, delta3, delta_sum = 0, delta2_sum = 0, delta3_sum = 0, delta_delta, delta_delta_sum = 0, prev_delta, delta_max = 0, delta2_max = 0, delta3_max = 0; char str[100]; double freq; int type; if (argc <= 1 || argc > 4) { usage: fprintf(stderr, "usage: test-mutex [Mutex|Sem|Rsem|Wsem|Vfs creat+unlink] \n"); exit(-1); usage2: fprintf(stderr, "the Mutex/Sem/Rsem/Wsem tests are not available.\n"); goto usage; } switch (argv[1][0]) { case 'M': type = TYPE_MUTEX; goto usage2; break; case 'S': type = TYPE_SEM; goto usage2; break; case 'R': type = TYPE_RSEM; goto usage2; break; case 'W': type = TYPE_WSEM; goto usage2; break; case 'V': type = TYPE_VFS; break; default: goto usage; } system("rm -f /tmp/* 2>/dev/null >/dev/null"); cpus = system("exit `grep processor /proc/cpuinfo | wc -l`"); cpus = WEXITSTATUS(cpus); tasks = cpus; if (argc >= 3) { tasks = atol(argv[2]); if (!tasks) goto usage; } if (argc >= 4) seconds = atol(argv[3]); else seconds = -1; #ifdef __ia64__ setup_mmtimer(); #endif printf("%ld CPUs, running %ld parallel test-tasks.\n", cpus, tasks); printf("checking %s performance.\n", type_names[type]); shared = setup_shared_var(); signal(SIGINT, sigint); signal(SIGHUP, sigint); parent = getpid(); for (i = 0; i < tasks; i++) if (!fork()) break; sleep(1); me = getpid(); sprintf(str, "/tmp/tmp-%d", me); if (me == parent) { unsigned long long total_count; int i = 0, j; for (;;) { sleep(1); if (i == seconds || SHARED(END)) break; i++; print_status(shared); } atomic_inc(&SHARED(END)); total_count = SHARED(SUM); for (j = 0; j < tasks; j++) wait(NULL); if (i) printf("\navg ops/sec: %Ld\n", total_count / i); LOCK(shared); // printf("delta_sum: %Ld\n", SHARED_LL(DELTA_SUM)); // printf("delta_delta_sum: %Ld\n", SHARED_LL(DELTA_DELTA_SUM)); #ifdef __ia64__ freq = 25.0; #else freq = 700.0; #endif printf("average cost per op: %.2f usecs\n", (double)SHARED_LL(DELTA_SUM)/total_count/freq); printf("average cost per lock: %.2f usecs\n", (double)SHARED_LL(DELTA2_SUM)/total_count/freq); printf("average cost per unlock: %.2f usecs\n", (double)SHARED_LL(DELTA3_SUM)/total_count/freq); printf("max cost per op: %.2f usecs\n", (double)SHARED_LL(DELTA_MAX)/freq); printf("max cost per lock: %.2f usecs\n", (double)SHARED_LL(DELTA2_MAX)/freq); printf("max cost per unlock: %.2f usecs\n", (double)SHARED_LL(DELTA3_MAX)/freq); printf("average deviance per op: %.2f usecs\n", (double)SHARED_LL(DELTA_DELTA_SUM)/total_count/freq/2.0); UNLOCK(shared); exit(0); } for (;;) { rdtscll(t0); switch (type) { case TYPE_MUTEX: mutex_lock(); rdtscll(t01); mutex_unlock(); break; case TYPE_SEM: down(); rdtscll(t01); up(); break; case TYPE_RSEM: down_read(); rdtscll(t01); up_read(); break; case TYPE_WSEM: down_write(); rdtscll(t01); up_write(); break; case TYPE_VFS: { int fd; fd = creat(str, S_IRWXU); rdtscll(t01); close(fd); break; } } rdtscll(t1); delta = t1-t0; if (unlikely(delta > delta_max)) delta_max = delta; delta_sum += delta; delta2 = t01-t0; if (unlikely(delta2 > delta2_max)) delta2_max = delta2; delta2_sum += delta2; delta3 = t1-t01; if (unlikely(delta3 > delta3_max)) delta3_max = delta3; delta3_sum += delta3; if (!first) { if (prev_delta < delta) delta_delta = delta - prev_delta; else delta_delta = prev_delta - delta; delta_delta_sum += delta_delta; #if 0 printf("%Ld-%Ld {%Ld} prev: {%Ld} / [%Ld]\n", t0, t1, delta, prev_delta, delta_delta); printf(" {%Ld} - {%Ld}\n", delta_sum, delta_delta_sum); #endif } else first = 0; prev_delta = delta; atomic_inc(&SHARED(COUNT)); if (unlikely(SHARED(END))) { LOCK(shared); SHARED_LL(DELTA_SUM) += delta_sum; SHARED_LL(DELTA_MAX) = max(SHARED_LL(DELTA_MAX), delta_max); SHARED_LL(DELTA2_SUM) += delta2_sum; SHARED_LL(DELTA2_MAX) = max(SHARED_LL(DELTA2_MAX), delta2_max); SHARED_LL(DELTA3_SUM) += delta3_sum; SHARED_LL(DELTA3_MAX) = max(SHARED_LL(DELTA3_MAX), delta3_max); SHARED_LL(DELTA_DELTA_SUM) += delta_delta_sum; #if 0 printf("delta_sum: %Ld\n", delta_sum); printf("delta_delta_sum: %Ld\n", delta_delta_sum); printf("DELTA_SUM: %Ld\n", SHARED_LL(DELTA_SUM)); printf("DELTA_DELTA_SUM: %Ld\n", SHARED_LL(DELTA_DELTA_SUM)); #endif UNLOCK(shared); exit(0); } } return 0; } From schwab at suse.de Sun Jan 8 21:37:12 2006 From: schwab at suse.de (Andreas Schwab) Date: Sun, 08 Jan 2006 11:37:12 +0100 Subject: [PATCH] powerpc: Fix PowerMac sound i2c In-Reply-To: <1136695956.30123.44.camel@localhost.localdomain> (Benjamin Herrenschmidt's message of "Sun, 08 Jan 2006 15:52:36 +1100") References: <1136695956.30123.44.camel@localhost.localdomain> Message-ID: Benjamin Herrenschmidt writes: > My patch reworking the PowerMac i2c code break the sound drivers as they > used to rely on some broken behaviour of i2c-keywest that is gone now. I'm not sure, but from looking at the other i2c drivers I'd rather think that the old behaviour of i2c-keywest was correct. There are only a few that implement both I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA, but none of them write the length byte together with the data. The commands are ony different in behaviour when reading: with I2C_SMBUS_I2C_BLOCK_DATA a fixed sized block is read from the bus, whereas with I2C_SMBUS_BLOCK_DATA the size of the block is variable. At least that's how i2c-nforce2, i2c-viapro and i2c-amd8111 implement the commands. Andreas. -- Andreas Schwab, SuSE Labs, schwab at suse.de SuSE Linux Products GmbH, Maxfeldstra?e 5, 90409 N?rnberg, Germany PGP key fingerprint = 58CA 54C7 6D53 942B 1756 01D3 44D5 214B 8276 4ED5 "And now for something completely different." From mingo at elte.hu Sun Jan 8 21:43:57 2006 From: mingo at elte.hu (Ingo Molnar) Date: Sun, 8 Jan 2006 11:43:57 +0100 Subject: PowerPC fastpaths for mutex subsystem In-Reply-To: <43BFFF1D.7030007@austin.ibm.com> References: <20060104144151.GA27646@elte.hu> <43BC5E15.207@austin.ibm.com> <20060105143502.GA16816@elte.hu> <43BD4C66.60001@austin.ibm.com> <20060105222106.GA26474@elte.hu> <43BDA672.4090704@austin.ibm.com> <20060106002919.GA29190@pb15.lixom.net> <43BFFF1D.7030007@austin.ibm.com> Message-ID: <20060108104357.GB31359@elte.hu> looks good to me. Minor nit: > +" isync\n" > +" isync \n" shouldnt these two be ISYNC_ON_SMP? Ingo From schwab at suse.de Mon Jan 9 00:30:36 2006 From: schwab at suse.de (Andreas Schwab) Date: Sun, 08 Jan 2006 14:30:36 +0100 Subject: [PATCH] powerpc: Fix PowerMac sound i2c In-Reply-To: (Andreas Schwab's message of "Sun, 08 Jan 2006 11:37:12 +0100") References: <1136695956.30123.44.camel@localhost.localdomain> Message-ID: Andreas Schwab writes: > Benjamin Herrenschmidt writes: > >> My patch reworking the PowerMac i2c code break the sound drivers as they >> used to rely on some broken behaviour of i2c-keywest that is gone now. > > I'm not sure, but from looking at the other i2c drivers I'd rather think > that the old behaviour of i2c-keywest was correct. I have now read the thread on linux-kernel@ and I have to revise my opinion. I think I now understand better the difference between smbus and i2c bus. Andreas. -- Andreas Schwab, SuSE Labs, schwab at suse.de SuSE Linux Products GmbH, Maxfeldstra?e 5, 90409 N?rnberg, Germany PGP key fingerprint = 58CA 54C7 6D53 942B 1756 01D3 44D5 214B 8276 4ED5 "And now for something completely different." From benh at kernel.crashing.org Mon Jan 9 08:03:33 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Mon, 09 Jan 2006 08:03:33 +1100 Subject: [PATCH] powerpc: Fix PowerMac sound i2c In-Reply-To: References: <1136695956.30123.44.camel@localhost.localdomain> Message-ID: <1136754214.30123.50.camel@localhost.localdomain> On Sun, 2006-01-08 at 11:37 +0100, Andreas Schwab wrote: > Benjamin Herrenschmidt writes: > > > My patch reworking the PowerMac i2c code break the sound drivers as they > > used to rely on some broken behaviour of i2c-keywest that is gone now. > > I'm not sure, but from looking at the other i2c drivers I'd rather think > that the old behaviour of i2c-keywest was correct. There are only a few > that implement both I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA, but > none of them write the length byte together with the data. The commands > are ony different in behaviour when reading: with I2C_SMBUS_I2C_BLOCK_DATA > a fixed sized block is read from the bus, whereas with > I2C_SMBUS_BLOCK_DATA the size of the block is variable. At least that's > how i2c-nforce2, i2c-viapro and i2c-amd8111 implement the commands. They are probably all wrong :) Look at the fallback implementation of smbus using base i2c in i2c-core.c .. I also checked the smbus spec and indeed, an smbus block transfer has the lenght on the wire. Ben From benh at kernel.crashing.org Mon Jan 9 10:37:56 2006 From: benh at kernel.crashing.org (Benjamin Herrenschmidt) Date: Mon, 09 Jan 2006 10:37:56 +1100 Subject: [PATCH] powerpc: Fix PowerMac sound i2c In-Reply-To: <1136701380.30123.46.camel@localhost.localdomain> References: <1136695956.30123.44.camel@localhost.localdomain> <1136701380.30123.46.camel@localhost.localdomain> Message-ID: <1136763476.30123.98.camel@localhost.localdomain> Ok, here's another version that uses the proper wrapper in the i2c layer, that was removed but is coming back... Index: linux-work/sound/ppc/tumbler.c =================================================================== --- linux-work.orig/sound/ppc/tumbler.c 2006-01-09 10:29:54.000000000 +1100 +++ linux-work/sound/ppc/tumbler.c 2006-01-09 10:30:14.000000000 +1100 @@ -137,7 +137,6 @@ static int send_init_client(pmac_keywest return 0; } - static int tumbler_init_client(pmac_keywest_t *i2c) { static unsigned int regs[] = { @@ -239,8 +238,8 @@ static int tumbler_set_master_volume(pma block[4] = (right_vol >> 8) & 0xff; block[5] = (right_vol >> 0) & 0xff; - if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_VOL, - 6, block) < 0) { + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, TAS_REG_VOL, 6, + block) < 0) { snd_printk("failed to set volume \n"); return -EINVAL; } @@ -340,8 +339,8 @@ static int tumbler_set_drc(pmac_tumbler_ val[1] = 0; } - if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_DRC, - 2, val) < 0) { + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, TAS_REG_DRC, + 2, val) < 0) { snd_printk("failed to set DRC\n"); return -EINVAL; } @@ -376,8 +375,8 @@ static int snapper_set_drc(pmac_tumbler_ val[4] = 0x60; val[5] = 0xa0; - if (i2c_smbus_write_block_data(mix->i2c.client, TAS_REG_DRC, - 6, val) < 0) { + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, TAS_REG_DRC, + 6, val) < 0) { snd_printk("failed to set DRC\n"); return -EINVAL; } @@ -481,8 +480,8 @@ static int tumbler_set_mono_volume(pmac_ vol = info->table[vol]; for (i = 0; i < info->bytes; i++) block[i] = (vol >> ((info->bytes - i - 1) * 8)) & 0xff; - if (i2c_smbus_write_block_data(mix->i2c.client, info->reg, - info->bytes, block) < 0) { + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, info->reg, + info->bytes, block) < 0) { snd_printk("failed to set mono volume %d\n", info->index); return -EINVAL; } @@ -611,7 +610,8 @@ static int snapper_set_mix_vol1(pmac_tum for (j = 0; j < 3; j++) block[i * 3 + j] = (vol >> ((2 - j) * 8)) & 0xff; } - if (i2c_smbus_write_block_data(mix->i2c.client, reg, 9, block) < 0) { + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, reg, + 9, block) < 0) { snd_printk("failed to set mono volume %d\n", reg); return -EINVAL; } Index: linux-work/sound/ppc/pmac.c =================================================================== --- linux-work.orig/sound/ppc/pmac.c 2006-01-09 10:29:54.000000000 +1100 +++ linux-work/sound/ppc/pmac.c 2006-01-09 10:30:14.000000000 +1100 @@ -74,7 +74,7 @@ static int snd_pmac_dbdma_alloc(pmac_t * static void snd_pmac_dbdma_free(pmac_t *chip, pmac_dbdma_t *rec) { - if (rec) { + if (rec->space) { unsigned int rsize = sizeof(struct dbdma_cmd) * (rec->size + 1); dma_free_coherent(&chip->pdev->dev, rsize, rec->space, rec->dma_base); @@ -895,6 +895,7 @@ static int __init snd_pmac_detect(pmac_t chip->can_capture = 1; chip->num_freqs = ARRAY_SIZE(awacs_freqs); chip->freq_table = awacs_freqs; + chip->pdev = NULL; chip->control_mask = MASK_IEPC | MASK_IEE | 0x11; /* default */ Index: linux-work/sound/oss/dmasound/tas_common.h =================================================================== --- linux-work.orig/sound/oss/dmasound/tas_common.h 2006-01-09 10:29:54.000000000 +1100 +++ linux-work/sound/oss/dmasound/tas_common.h 2006-01-09 10:31:09.000000000 +1100 @@ -178,10 +178,10 @@ tas_write_register( struct tas_data_t *s if (write_mode & WRITE_SHADOW) memcpy(self->shadow[reg_num],data,reg_width); if (write_mode & WRITE_HW) { - rc=i2c_smbus_write_block_data(self->client, - reg_num, - reg_width, - data); + rc=i2c_smbus_write_i2c_block_data(self->client, + reg_num, + reg_width, + data); if (rc < 0) { printk("tas: I2C block write failed \n"); return rc; @@ -199,10 +199,10 @@ tas_sync_register( struct tas_data_t *se if (reg_width==0 || self==NULL) return -EINVAL; - rc=i2c_smbus_write_block_data(self->client, - reg_num, - reg_width, - self->shadow[reg_num]); + rc=i2c_smbus_write_i2c_block_data(self->client, + reg_num, + reg_width, + self->shadow[reg_num]); if (rc < 0) { printk("tas: I2C block write failed \n"); return rc; From anton at samba.org Mon Jan 9 15:41:31 2006 From: anton at samba.org (Anton Blanchard) Date: Mon, 9 Jan 2006 15:41:31 +1100 Subject: [PATCH] ppc64: Fix oprofile when compiled as a module In-Reply-To: <20051229105131.GC18479@krispykreme> References: <20051229105131.GC18479@krispykreme> Message-ID: <20060109044131.GP26499@krispykreme> My recent changes to oprofile broke it when built as a module. Fix it by using an enum instead of a function pointer. This way we still retain the oprofile configuration in the cputable. Signed-off-by: Anton Blanchard --- updated for current powerpc git tree. Index: build/arch/powerpc/kernel/cputable.c =================================================================== --- build.orig/arch/powerpc/kernel/cputable.c 2006-01-09 13:31:50.000000000 +1100 +++ build/arch/powerpc/kernel/cputable.c 2006-01-09 14:27:19.000000000 +1100 @@ -78,10 +78,8 @@ .dcache_bsize = 128, .num_pmcs = 8, .cpu_setup = __setup_cpu_power3, -#ifdef CONFIG_OPROFILE .oprofile_cpu_type = "ppc64/power3", - .oprofile_model = &op_model_rs64, -#endif + .oprofile_type = RS64, }, { /* Power3+ */ .pvr_mask = 0xffff0000, @@ -93,10 +91,8 @@ .dcache_bsize = 128, .num_pmcs = 8, .cpu_setup = __setup_cpu_power3, -#ifdef CONFIG_OPROFILE .oprofile_cpu_type = "ppc64/power3", - .oprofile_model = &op_model_rs64, -#endif + .oprofile_type = RS64, }, { /* Northstar */ .pvr_mask = 0xffff0000, @@ -108,10 +104,8 @@ .dcache_bsize = 128, .num_pmcs = 8, .cpu_setup = __setup_cpu_power3, -#ifdef CONFIG_OPROFILE .oprofile_cpu_type = "ppc64/rs64", - .oprofile_model = &op_model_rs64, -#endif + .oprofile_type = RS64, }, { /* Pulsar */ .pvr_mask = 0xffff0000, @@ -123,10 +117,8 @@ .dcache_bsize = 128, .num_pmcs = 8, .cpu_setup = __setup_cpu_power3, -#ifdef CONFIG_OPROFILE .oprofile_cpu_type = "ppc64/rs64", - .oprofile_model = &op_model_rs64, -#endif + .oprofile_type = RS64, }, { /* I-star */ .pvr_mask = 0xffff0000, @@ -138,10 +130,8 @@ .dcache_bsize = 128, .num_pmcs = 8, .cpu_setup = __setup_cpu_power3, -#ifdef CONFIG_OPROFILE .oprofile_cpu_type = "ppc64/rs64", - .oprofile_model = &op_model_rs64, -#endif + .oprofile_type = RS64, }, { /* S-star */ .pvr_mask = 0xffff0000, @@ -153,10 +143,8 @@ .dcache_bsize = 128, .num_pmcs = 8, .cpu_setup = __setup_cpu_power3, -#ifdef CONFIG_OPROFILE .oprofile_cpu_type = "ppc64/rs64", - .oprofile_model = &op_model_rs64, -#endif + .oprofile_type = RS64, }, { /* Power4 */ .pvr_mask = 0xffff0000, @@ -168,10 +156,8 @@ .dcache_bsize = 128, .num_pmcs = 8, .cpu_setup = __setup_cpu_power4, -#ifdef CONFIG_OPROFILE .oprofile_cpu_type = "ppc64/power4", - .oprofile_model = &op_model_rs64, -#endif + .oprofile_type = POWER4, }, { /* Power4+ */ .pvr_mask = 0xffff0000, @@ -183,10 +169,8 @@ .dcache_bsize = 128, .num_pmcs = 8, .cpu_setup = __setup_cpu_power4, -#ifdef CONFIG_OPROFILE .oprofile_cpu_type = "ppc64/power4", - .oprofile_model = &op_model_power4, -#endif + .oprofile_type = POWER4, }, { /* PPC970 */ .pvr_mask = 0xffff0000, @@ -199,10 +183,8 @@ .dcache_bsize = 128, .num_pmcs = 8, .cpu_setup = __setup_cpu_ppc970, -#ifdef CONFIG_OPROFILE .oprofile_cpu_type