LinuxPPC-Dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [BUG] 2.6.30-rc3: BUG triggered on some hugepage usages
From: Linus Torvalds @ 2009-05-18 17:26 UTC (permalink / raw)
  To: Mel Gorman; +Cc: ebmunson, Linux Kernel Mailing List, linuxppc-dev
In-Reply-To: <20090518171322.GB13093@csn.ul.ie>



On Mon, 18 May 2009, Mel Gorman wrote:
> 
> What's the story with this patch? I'm still hearing of failures with huge pages
> that this patch fixes but I'm no seeing it upstream. Was the patch
> rejected or did it just slip through the cracks?

It didn't slip through the cracks, it was apparently just delayed. It's 
part of the merge requests I've gotten today (well, strictly speaking it 
seems to have hit my inbox just before midnight yesterday, but that's 
because those silly aussies stand upside down and sleep at odd hours).

In fact, I just merged it, I haven't even had time to push that out. 

		Linus

^ permalink raw reply

* Re: fs_enet build breakage
From: Scott Wood @ 2009-05-18 17:20 UTC (permalink / raw)
  To: Kumar Gala; +Cc: linuxppc-dev list, Kumar Gala
In-Reply-To: <9732983B-B67C-4831-BBAB-65472BE0F279@kernel.crashing.org>

On Mon, May 18, 2009 at 08:23:17AM -0500, Kumar Gala wrote:
> 
> On May 18, 2009, at 2:01 AM, Benjamin Herrenschmidt wrote:
> 
> >Hi Kumar
> >
> >Commit 4484079d517c2b6521621be0b1ea246ccc55c7d7 from your next branch
> >breaks my 8xx test config with the following error:
> >
> >/home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c: In  
> >function ‘setup_immap’:
> >/home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c: 
> >947: error: ‘IMAP_ADDR’ undeclared (first use in this function)
> >/home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c: 
> >947: error: (Each undeclared identifier is reported only once
> >/home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c: 
> >947: error: for each function it appears in.)
> >
> >I'm still pushing it with a bunch of other stuff to "test" so people  
> >get
> >to find more breakage but it will need to be fixed for the actual  
> >"next"
> >branch.
> >
> >Cheers,
> >Ben.
> >
> 
> I'm pretty sure the driver changes to address this are in dave's net- 
> next tree.

Won't this break git bisect?

Not to mention anyone trying to use your tree now...

-Scott

^ permalink raw reply

* Re: [BUG] 2.6.30-rc3: BUG triggered on some hugepage usages
From: Mel Gorman @ 2009-05-18 17:13 UTC (permalink / raw)
  To: Benjamin Herrenschmidt
  Cc: linuxppc-dev, Linus Torvalds, Linux Kernel Mailing List, ebmunson
In-Reply-To: <1241128126.29501.48.camel@pasglop>

On Fri, May 01, 2009 at 07:48:46AM +1000, Benjamin Herrenschmidt wrote:
> On Thu, 2009-04-30 at 21:59 +0100, Mel Gorman wrote:
> 
> > This patch fixes the problem by not asseting the PTE is locked for VMAs
> > backed by huge pages.
> 
> Thanks, will apply.
> 

What's the story with this patch? I'm still hearing of failures with huge pages
that this patch fixes but I'm no seeing it upstream. Was the patch
rejected or did it just slip through the cracks?

To refresh, an assertion is being made on ppc64 that only makes sense for
base pages. Hugepages through a wobbly every time. For convenience, here is
the patch again.

Thanks.

==== CUT HERE ====
powerpc: Do not assert pte_locked for hugepage PTE entries

With DEBUG_VM enabled, an assertion is made when changing the protection
flags of a PTE that the PTE is locked. Huge pages use a different
pagetable format and the assertion is bogus and will always trigger with
a bug looking something like

 Unable to handle kernel paging request for data at address 0xf1a00235800006f8
 Faulting instruction address: 0xc000000000034a80
 Oops: Kernel access of bad area, sig: 11 [#1]
 SMP NR_CPUS=32 NUMA Maple
 Modules linked in: dm_snapshot dm_mirror dm_region_hash
  dm_log dm_mod loop evdev ext3 jbd mbcache sg sd_mod ide_pci_generic
  pata_amd ata_generic ipr libata tg3 libphy scsi_mod windfarm_pid
  windfarm_smu_sat windfarm_max6690_sensor windfarm_lm75_sensor
  windfarm_cpufreq_clamp windfarm_core i2c_powermac
 NIP: c000000000034a80 LR: c000000000034b18 CTR: 0000000000000003
 REGS: c000000003037600 TRAP: 0300   Not tainted (2.6.30-rc3-autokern1)
 MSR: 9000000000009032 <EE,ME,IR,DR>  CR: 28002484  XER: 200fffff
 DAR: f1a00235800006f8, DSISR: 0000000040010000
 TASK = c0000002e54cc740[2960] 'map_high_trunca' THREAD: c000000003034000 CPU: 2
 GPR00: 4000000000000000 c000000003037880 c000000000895d30 c0000002e5a2e500
 GPR04: 00000000a0000000 c0000002edc40880 0000005700000393 0000000000000001
 GPR08: f000000011ac0000 01a00235800006e8 00000000000000f5 f1a00235800006e8
 GPR12: 0000000028000484 c0000000008dd780 0000000000001000 0000000000000000
 GPR16: fffffffffffff000 0000000000000000 00000000a0000000 c000000003037a20
 GPR20: c0000002e5f4ece8 0000000000001000 c0000002edc40880 0000000000000000
 GPR24: c0000002e5f4ece8 0000000000000000 00000000a0000000 c0000002e5f4ece8
 GPR28: 0000005700000393 c0000002e5a2e500 00000000a0000000 c000000003037880
 NIP [c000000000034a80] .assert_pte_locked+0xa4/0xd0
 LR [c000000000034b18] .ptep_set_access_flags+0x6c/0xb4
 Call Trace:
 [c000000003037880] [c000000003037990] 0xc000000003037990 (unreliable)
 [c000000003037910] [c000000000034b18] .ptep_set_access_flags+0x6c/0xb4
 [c0000000030379b0] [c00000000014bef8] .hugetlb_cow+0x124/0x674
 [c000000003037b00] [c00000000014c930] .hugetlb_fault+0x4e8/0x6f8
 [c000000003037c00] [c00000000013443c] .handle_mm_fault+0xac/0x828
 [c000000003037cf0] [c0000000000340a8] .do_page_fault+0x39c/0x584
 [c000000003037e30] [c0000000000057b0] handle_page_fault+0x20/0x5c
 Instruction dump:
 7d29582a 7d200074 7800d182 0b000000 3c004000 3960ffff 780007c6 796b00c4
 7d290214 7929a302 1d290068 7d6b4a14 <800b0010> 7c000074 7800d182 0b000000

This patch fixes the problem by not asseting the PTE is locked for VMAs
backed by huge pages.

Signed-off-by: Mel Gorman <mel@csn.ul.ie>
--- 
 arch/powerpc/mm/pgtable.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/powerpc/mm/pgtable.c b/arch/powerpc/mm/pgtable.c
index f5c6fd4..ae1d67c 100644
--- a/arch/powerpc/mm/pgtable.c
+++ b/arch/powerpc/mm/pgtable.c
@@ -219,7 +219,8 @@ int ptep_set_access_flags(struct vm_area_struct *vma, unsigned long address,
 		entry = do_dcache_icache_coherency(entry);
 	changed = !pte_same(*(ptep), entry);
 	if (changed) {
-		assert_pte_locked(vma->vm_mm, address);
+		if (!(vma->vm_flags & VM_HUGETLB))
+			assert_pte_locked(vma->vm_mm, address);
 		__ptep_set_access_flags(ptep, entry);
 		flush_tlb_page_nohash(vma, address);
 	}

^ permalink raw reply related

* Re: Accessing NTFS Shares from Freescale MPC8313
From: Scott Wood @ 2009-05-18 17:10 UTC (permalink / raw)
  To: Chris Plasun; +Cc: linuxppc-dev
In-Reply-To: <4A0FCEAA.5000606@yahoo.com>

On Sun, May 17, 2009 at 01:45:30AM -0700, Chris Plasun wrote:
> Hi,
> 
> (I haven't found any answers in the archives)
> 
> How would I access a NTFS shared directory from a Freescale MPC8313?
> 
> Will Samba run on a Freescale MPC8313?

It should (and if it doesn't, file a bug with them).  

Userspace is pretty much the same at the source-code and user-interface
level -- the answer to "how do I do X on this PPC board" is generally the
same as the answer to "how do I do X on Linux", other than that you may
need to compile some packages manually if they don't come with whatever
distribution you're using.

-Scott

^ permalink raw reply

* Re: [RFC Patch 2/6] Introduce PPC64 specific Hardware Breakpointinterfaces
From: Alan Stern @ 2009-05-18 16:30 UTC (permalink / raw)
  To: K.Prasad
  Cc: Michael Neuling, Benjamin Herrenschmidt, linuxppc-dev, paulus,
	Roland McGrath
In-Reply-To: <20090518161055.GA27641@in.ibm.com>

On Mon, 18 May 2009, K.Prasad wrote:

> > > > > +int __kprobes hw_breakpoint_handler(struct die_args *args)
> > > > > +{
> > > > > +	int rc = NOTIFY_STOP;
> > > > > +	struct hw_breakpoint *bp;
> > > > > +	struct pt_regs *regs = args->regs;
> > > > > +	unsigned long dar;
> > > > > +	int cpu, stepped, is_kernel;
> > > > > +
> > > > > +	/* Disable breakpoints during exception handling */
> > > > > +	set_dabr(0);
> > > > > +
> > > > > +	dar = regs->dar & (~HW_BREAKPOINT_ALIGN);
> > > > > +	is_kernel = (dar >= TASK_SIZE) ? 1 : 0;
> > > > 
> > > > is_kernel_addr() ?
> > > > 
> > > 
> > > Ok.
> > 
> > Shouldn't this test hbp_kernel_pos instead?
> > 
> 
> Testing hbp_kernel_pos should be sufficient for PPC64 with just one
> breakpoint register. However the above code is more extensible to other
> PowerPC implementations which have more than one breakpoint register.

Then maybe you don't want to test this at all.  Just compare the dar 
value with each of the breakpoint addresses.  That's more like what the 
x86 code does.

Alan Stern

^ permalink raw reply

* Re: [RFC Patch 1/6] Prepare the PowerPC platform for HW Breakpoint infrastructure
From: K.Prasad @ 2009-05-18 16:15 UTC (permalink / raw)
  To: Benjamin Herrenschmidt
  Cc: linuxppc-dev, Michael Neuling, Alan Stern, Roland McGrath, paulus
In-Reply-To: <1242617732.18075.29.camel@pasglop>

On Mon, May 18, 2009 at 01:35:32PM +1000, Benjamin Herrenschmidt wrote:
> On Thu, 2009-05-14 at 19:13 +0530, K.Prasad wrote:
> > plain text document attachment (ppc64_prepare_code_01)
> > Prepare the PowerPC code for HW Breakpoint infrastructure patches by including
> > relevant constant definitions and function declarations.
> 
> Hi !
> 
> Some comments below...
> 
> > +#define   HBP_NUM	1	/* Number of physical HW breakpoint registers */
> 
> Can you use a more verbose constant ? reg.h is included everywhere so
> the risk of collision is high.
>

This constant is used by the generic HW Breakpoint code in
kernel/hw_breakpoint.c too and renaming it here will require changes
there too, while I don't see any existing name-space clashes.

Instead the definition can be moved into arch/powerpc/include/asm/hw_breakpoint.h
and in arch/power/include/asm/processor.h. What do you think?
 
> >  #define   DABR_TRANSLATION	(1UL << 2)
> >  #define   DABR_DATA_WRITE	(1UL << 1)
> >  #define   DABR_DATA_READ	(1UL << 0)
> > +#define   DABR_DATA_RW		(3UL << 0)
> 
> Do you really need that ? It's just DABR_DATA_WRITE | DABR_DATA_READ :-)
> 

Yes, it looks silly. I will remove it. The HW_BREAKPOINT_RW defined in
arch/powerpc/include/asm/hw_breakpoint.h will instead be defined as:

#define HW_BREAKPOINT_RW (DABR_DATA_READ | DABR_DATA_WRITE)


> Cheers,
> Ben.
>

Thanks,
K.Prasad
 

^ permalink raw reply

* Re: [RFC Patch 6/6] Adapt kexec and samples code to recognise PPC64hardware breakpoint usage
From: K.Prasad @ 2009-05-18 16:11 UTC (permalink / raw)
  To: Alan Stern
  Cc: linuxppc-dev, Michael Neuling, Benjamin Herrenschmidt, paulus,
	Roland McGrath
In-Reply-To: <Pine.LNX.4.44L0.0905141621040.4344-100000@iolanthe.rowland.org>

On Thu, May 14, 2009 at 04:21:48PM -0400, Alan Stern wrote:
> On Thu, 14 May 2009, K.Prasad wrote:
> 
> > Index: linux-2.6-tip.hbkpt/samples/hw_breakpoint/data_breakpoint.c
> > ===================================================================
> > --- linux-2.6-tip.hbkpt.orig/samples/hw_breakpoint/data_breakpoint.c	2009-05-14 00:17:24.000000000 +0530
> > +++ linux-2.6-tip.hbkpt/samples/hw_breakpoint/data_breakpoint.c	2009-05-14 00:58:06.000000000 +0530
> > @@ -54,6 +54,10 @@
> >  	sample_hbp.info.type = HW_BREAKPOINT_WRITE;
> >  	sample_hbp.info.len = HW_BREAKPOINT_LEN_4;
> >  #endif /* CONFIG_X86 */
> > +#ifdef CONFIG_PPC64
> > +	sample_hbp.info.name = ksym_name;
> > +	sample_hbp.info.type = DABR_DATA_WRITE;
> 
> This should be HW_BREAKPOINT_WRITE, not DABR_DATA_WRITE.
> 
> Alan Stern
>

Done. Thanks.

-- K.Prasad
 

^ permalink raw reply

* Re: [RFC Patch 2/6] Introduce PPC64 specific Hardware Breakpointinterfaces
From: K.Prasad @ 2009-05-18 16:10 UTC (permalink / raw)
  To: Alan Stern
  Cc: Michael Neuling, Benjamin Herrenschmidt, linuxppc-dev, paulus,
	Roland McGrath
In-Reply-To: <Pine.LNX.4.44L0.0905141614360.4344-100000@iolanthe.rowland.org>

On Thu, May 14, 2009 at 04:20:04PM -0400, Alan Stern wrote:
> On Fri, 15 May 2009, K.Prasad wrote:
> 
> > I see that you're referring to this code in __switch_to() :
> >         if (unlikely(__get_cpu_var(current_dabr) != new->thread.dabr))
> >                 set_dabr(new->thread.dabr);
> > 
> > arch_install_thread_hw_breakpoint()<--switch_to_thread_hw_breakpoint()
> > <--__switch_to() implementation is also similar.
> > 
> > In __switch_to(),
> >                 if (unlikely(test_tsk_thread_flag(new, TIF_DEBUG)))
> >                         switch_to_thread_hw_breakpoint(new);
> > 
> > happens only when TIF_DEBUG flag is set. This flag is cleared when the
> > process unregisters any breakpoints it had requested earlier. So, the
> > set_dabr() call is avoided for processes not using the debug register.
> 
> In the x86 code, shouldn't arch_update_user_hw_breakpoint set or clear
> TIF_DEBUG, depending on whether or not there are any user breakpoints
> remaining?
>

Yes. There's a bigger issue in setting TIF_DEBUG flag through ptrace
code. It should instead be done in register_user_hw_breakpoint() and
removed through unregister_user_hw_breakpoint() when the last breakpoint
request is being unregistered.

The unregister_user_hw_breakpoint() code should be like this:

void unregister_user_hw_breakpoint(struct task_struct *tsk,
						struct hw_breakpoint *bp)
{
	struct thread_struct *thread = &(tsk->thread);
	int i, pos = -1, clear_tsk_debug_counter = 0;

	spin_lock_bh(&hw_breakpoint_lock);
	for (i = 0; i < hbp_kernel_pos; i++) {
		if (thread->hbp[i])
			clear_tsk_debug_counter++;
		if (bp == thread->hbp[i]) {
			clear_tsk_debug_counter--;
			pos = i;
		}
	}
	if (pos >= 0)
		__unregister_user_hw_breakpoint(pos, tsk);

	if (!clear_tsk_debug_counter)
		clear_tsk_thread_flag(tsk, TIF_DEBUG);

	spin_unlock_bh(&hw_breakpoint_lock);
}

It needs modification in the generic HW Breakpoint code. I'm planning to
submit this as a patch over the earlier patchset (after it is pulled
into -tip tree).
 
> > > > +int __kprobes hw_breakpoint_handler(struct die_args *args)
> > > > +{
> > > > +	int rc = NOTIFY_STOP;
> > > > +	struct hw_breakpoint *bp;
> > > > +	struct pt_regs *regs = args->regs;
> > > > +	unsigned long dar;
> > > > +	int cpu, stepped, is_kernel;
> > > > +
> > > > +	/* Disable breakpoints during exception handling */
> > > > +	set_dabr(0);
> > > > +
> > > > +	dar = regs->dar & (~HW_BREAKPOINT_ALIGN);
> > > > +	is_kernel = (dar >= TASK_SIZE) ? 1 : 0;
> > > 
> > > is_kernel_addr() ?
> > > 
> > 
> > Ok.
> 
> Shouldn't this test hbp_kernel_pos instead?
> 

Testing hbp_kernel_pos should be sufficient for PPC64 with just one
breakpoint register. However the above code is more extensible to other
PowerPC implementations which have more than one breakpoint register.

> > > > +	if (is_kernel)
> > > > +		bp = hbp_kernel[0];
> > > > +	else {
> > > > +		bp = current->thread.hbp[0];
> > > > +		/* Lazy debug register switching */
> > > > +		if (!bp)
> > > > +			return rc;
> 
> Shouldn't this test be moved outside the "if" statement, as in the x86 
> code?
> 

Yes, I will do it. Another error here is the return code when exception
is raised from user-space address due to lazy debug register switching.
The return code should be NOTIFY_STOP (and not NOTIFY_DONE) since the
exception is a stray one and we don't want it to be propogated as a
signal to user-space. This change is required in both x86 and PPC64. I
will submit the x86 change as a separate patch.

> Alan Stern
> 

Thanks,
K.Prasad

^ permalink raw reply

* Re: FEC & SDMA (bestcomm) interaction on the 5200
From: Dimiter Popoff @ 2009-05-18 15:59 UTC (permalink / raw)
  To: linuxppc-dev

> From: Grant Likely <grant.likely@secretlab.ca>
> Date: Fri, 15 May 2009 08:52:19 -0600
> Subject: Re: FEC & SDMA (bestcomm) interaction on the 5200
> To: Dimiter Popoff <dp@tgi-sci.com>
> Cc: linuxppc-dev@ozlabs.org
> 
> On Fri, May 15, 2009 at 2:36 AM, Dimiter Popoff <dp@tgi-sci.com> wrote:
> > Hi people,
> >
> > I am porting my (not linux) OS to the 5200. I went all the
> > way understanding how the SDMA works and programming what
> > I needed for it so things are now pretty stable in terms
> > of disk I/O and system memory -> PCI (offscreen window
> > buffers -> PCI display framebuffer).
> >
> > =A0And I wanted to make use of the Ethernet ("FEC", as they have
> > it), hoping it would take me a few days (not my first one).
> >
> > =A0I am out of luck - it is practically undocumented. There is some
> > talk of receive buffers which the FEC should see empty etc.,
> > how on earth is that supposed to happen when it has no bus
> > master capability at all? It relies on the SDMA for its bus
> > activity - but in what format does it expect to get these
> > buffer related data?
> > =A0Or (more likely, at least hopefully so) this talk in the
> > MPC5200BUM (and MPC5200UM, for that) is just nonsense and
> > the FEC simply puts in the FIFO incoming packets, writing as
> > a last .l the receive frame status word? If so, I could
> > easily handle that with the SDMA.
> > =A0Same question about transmitting. I can only hope/assume
> > that the FEC will just send what it is handed through the
> > FIFO by the SDMA, the very first .l being the frame control
> > word, and ending when the TFD from the data drd1a or whatever
> > makes it through?
> 
> It's been a long time since I've been in the bowels of the FEC driver,
> but I believe you are correct.
> 
> g.
> 
> -- =
> 
> Grant Likely, B.Sc., P.Eng.
> Secret Lab Technologies Ltd.
>

Thanks for the input, Grant. It matches the memories of a guy
at Frrecale who has had to deal with it ass well, so I guess
I am on the right path - It won't be without trying this or
that out but I am well enough positioned to start.

Dimiter

------------------------------------------------------
Dimiter Popoff               Transgalactic Instruments

http://www.tgi-sci.com
------------------------------------------------------
http://www.flickr.com/photos/didi_tgi/sets/72157600228621276/

^ permalink raw reply

* Re: [PATCH 08/12] mpc5121: Added I2C support.
From: Wolfgang Grandegger @ 2009-05-18 14:29 UTC (permalink / raw)
  To: Piotr Zięcik; +Cc: linuxppc-dev, Wolfgang Denk, Detlev Zundel, linux-i2c
In-Reply-To: <200905181557.11766.kosmo@semihalf.com>

Piotr Zięcik wrote:
>> Right. Furthermore, the i2c-mpc.c should be extened to support bus speed
>> setting for the MPC512x, which has been merged recently (see commit id
>> f2bd5efe).
> 
> I have simple question about bus speed setting support. Existing 
> implementation uses default safe speed if there is no 'clock-frequency' 
> property in i2c node. Comments in code suggest that this behaviour is left 
> for backward compatibility only. Should I make the 'clock-frequency'
> property mandatory for a new type of I2C controller (MPC5121) ?

I don't think so, for the same backward compatibility reason as for the
other boards. But the DTS file might be changed to use clock-frequency.
BTW, when I wrote the code I already had the MPC512x in mind. IIRC, the
FDR table and the function scanning it for the MPC52xx should work for
the MPC512x as well. It's mainly a matter of using the proper function
to get the source clock frequency and fiddling with multiple-arch support.

Wolfgang.

^ permalink raw reply

* Re: [PATCH 08/12] mpc5121: Added I2C support.
From: Grant Likely @ 2009-05-18 14:11 UTC (permalink / raw)
  To: Piotr Zięcik; +Cc: linuxppc-dev, Detlev Zundel, Wolfgang Denk, linux-i2c
In-Reply-To: <200905181557.11766.kosmo@semihalf.com>

2009/5/18 Piotr Zi=EAcik <kosmo@semihalf.com>:
>> Right. Furthermore, the i2c-mpc.c should be extened to support bus speed
>> setting for the MPC512x, which has been merged recently (see commit id
>> f2bd5efe).
>
> I have simple question about bus speed setting support. Existing
> implementation uses default safe speed if there is no 'clock-frequency'
> property in i2c node. Comments in code suggest that this behaviour is lef=
t
> for backward compatibility only. Should I make the 'clock-frequency'
> property mandatory for a new type of I2C controller (MPC5121) ?

yes.  At least in documentation, but I wouldn't do extra work to
disable that behaviour for 5121 boards.

g.

--=20
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.

^ permalink raw reply

* Re: [PATCH 08/12] mpc5121: Added I2C support.
From: Piotr Zięcik @ 2009-05-18 13:57 UTC (permalink / raw)
  To: Wolfgang Grandegger; +Cc: linuxppc-dev, Wolfgang Denk, Detlev Zundel, linux-i2c
In-Reply-To: <4A028182.3040107@grandegger.com>

> Right. Furthermore, the i2c-mpc.c should be extened to support bus speed
> setting for the MPC512x, which has been merged recently (see commit id
> f2bd5efe).

I have simple question about bus speed setting support. Existing=20
implementation uses default safe speed if there is no 'clock-frequency'=20
property in i2c node. Comments in code suggest that this behaviour is left=
=20
for backward compatibility only. Should I make the 'clock-frequency'
property mandatory for a new type of I2C controller (MPC5121) ?

=2D-=20
Best Regards.
Piotr Zi=EAcik

^ permalink raw reply

* Re: [PATCH V2 2/3] powerpc: Add support for swiotlb on 32-bit
From: Kumar Gala @ 2009-05-18 13:25 UTC (permalink / raw)
  To: Benjamin Herrenschmidt; +Cc: fujita.tomonori, linuxppc-dev
In-Reply-To: <1242622141.18075.37.camel@pasglop>


On May 17, 2009, at 11:49 PM, Benjamin Herrenschmidt wrote:

> On Thu, 2009-05-14 at 17:42 -0500, Becky Bruce wrote:
>> This patch includes the basic infrastructure to use swiotlb
>> bounce buffering on 32-bit powerpc.  It is not yet enabled on
>> any platforms.  Probably the most interesting bit is the
>> addition of addr_needs_map to dma_ops - we need this as
>> a dma_op because the decision of whether or not an addr
>> can be mapped by a device is device-specific.
>>
>> Signed-off-by: Becky Bruce <beckyb@kernel.crashing.org>
>
> Hi Becky !
>
> Finally I got to look at your patch :-)
>
> A few comments below...
>
>> #ifdef CONFIG_NOT_COHERENT_CACHE
>> /*
>>  * DMA-consistent mapping functions for PowerPCs that don't support
>> @@ -76,6 +85,8 @@ struct dma_mapping_ops {
>> 				dma_addr_t dma_address, size_t size,
>> 				enum dma_data_direction direction,
>> 				struct dma_attrs *attrs);
>> +	int		(*addr_needs_map)(struct device *dev, dma_addr_t addr,
>> +				size_t size);
>
> What annoys me here is that we basically end up with two indirect
> function calls for pretty much any DMA map. One was bad enough on low
> end processors or very intensive networking, but this is getting real
> bad don't you think ?
>
> Granted, this is only used when swiotlb is used too, but still...
>
> So the problem is that the region that can pass-through is somewhat
> a mix of bus specific (incoming DMA window location & size) and
> device specific (device addressing limitations).
>
> Now, if we can always reduce it to a single range though, which I
> think is practically the case, can't we instead pre-calculate that
> range and stick -that- in the struct dev archdata or similar thus
> speeding up the decision for a given address as to whether it needs
> a swiotlb mapping or not ? Or does it gets too messy ?

Part of this is how the generic swiotlb code works and part of it was  
our desire not to bloat dev archdata by adding such info that as you  
say is either bus specific or conveyed in the dma addr mask.

- k

^ permalink raw reply

* Re: fs_enet build breakage
From: Kumar Gala @ 2009-05-18 13:23 UTC (permalink / raw)
  To: Benjamin Herrenschmidt; +Cc: Kumar Gala, linuxppc-dev list
In-Reply-To: <1242630064.18075.48.camel@pasglop>


On May 18, 2009, at 2:01 AM, Benjamin Herrenschmidt wrote:

> Hi Kumar
>
> Commit 4484079d517c2b6521621be0b1ea246ccc55c7d7 from your next branch
> breaks my 8xx test config with the following error:
>
> /home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c: In =20=

> function =91setup_immap=92:
> /home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c:=20
> 947: error: =91IMAP_ADDR=92 undeclared (first use in this function)
> /home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c:=20
> 947: error: (Each undeclared identifier is reported only once
> /home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c:=20
> 947: error: for each function it appears in.)
>
> I'm still pushing it with a bunch of other stuff to "test" so people =20=

> get
> to find more breakage but it will need to be fixed for the actual =20
> "next"
> branch.
>
> Cheers,
> Ben.
>

I'm pretty sure the driver changes to address this are in dave's net-=20
next tree.

- k=

^ permalink raw reply

* [PATCH v3] powerpc/pci: clean up direct access to sysdata by iseries platform
From: Kumar Gala @ 2009-05-18 12:56 UTC (permalink / raw)
  To: linuxppc-dev; +Cc: Stephen Rothwell

We shouldn't directly access sysdata to get the device node.  We should
be calling pci_device_to_OF_node().

Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
---
* Fixed compile error (dev -> pdev)

 arch/powerpc/platforms/iseries/iommu.c |    2 +-
 arch/powerpc/platforms/iseries/pci.c   |    8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/arch/powerpc/platforms/iseries/iommu.c b/arch/powerpc/platforms/iseries/iommu.c
index 4021982..6c1e101 100644
--- a/arch/powerpc/platforms/iseries/iommu.c
+++ b/arch/powerpc/platforms/iseries/iommu.c
@@ -177,7 +177,7 @@ static struct iommu_table *iommu_table_find(struct iommu_table * tbl)
 static void pci_dma_dev_setup_iseries(struct pci_dev *pdev)
 {
 	struct iommu_table *tbl;
-	struct device_node *dn = pdev->sysdata;
+	struct device_node *dn = pci_device_to_OF_node(pdev);
 	struct pci_dn *pdn = PCI_DN(dn);
 	const u32 *lsn = of_get_property(dn, "linux,logical-slot-number", NULL);
 
diff --git a/arch/powerpc/platforms/iseries/pci.c b/arch/powerpc/platforms/iseries/pci.c
index 21cddc3..175aac8 100644
--- a/arch/powerpc/platforms/iseries/pci.c
+++ b/arch/powerpc/platforms/iseries/pci.c
@@ -318,6 +318,7 @@ static void __init iomm_table_allocate_entry(struct pci_dev *dev, int bar_num)
 {
 	struct resource *bar_res = &dev->resource[bar_num];
 	long bar_size = pci_resource_len(dev, bar_num);
+	struct device_node *dn = pci_device_to_OF_node(dev);
 
 	/*
 	 * No space to allocate, quick exit, skip Allocation.
@@ -335,9 +336,9 @@ static void __init iomm_table_allocate_entry(struct pci_dev *dev, int bar_num)
 	 * Allocate the number of table entries needed for BAR.
 	 */
 	while (bar_size > 0 ) {
-		iomm_table[current_iomm_table_entry] = dev->sysdata;
+		iomm_table[current_iomm_table_entry] = dn;
 		ds_addr_table[current_iomm_table_entry] =
-			iseries_ds_addr(dev->sysdata) | (bar_num << 24);
+			iseries_ds_addr(dn) | (bar_num << 24);
 		bar_size -= IOMM_TABLE_ENTRY_SIZE;
 		++current_iomm_table_entry;
 	}
@@ -410,7 +411,7 @@ void __init iSeries_pcibios_fixup_resources(struct pci_dev *pdev)
 	struct device_node *node;
 	int i;
 
-	node = find_device_node(bus, pdev->devfn);
+	node = pci_device_to_OF_node(pdev);
 	pr_debug("PCI: iSeries %s, pdev %p, node %p\n",
 		 pci_name(pdev), pdev, node);
 	if (!node) {
@@ -441,7 +442,6 @@ void __init iSeries_pcibios_fixup_resources(struct pci_dev *pdev)
 		}
 	}
 
-	pdev->sysdata = node;
 	allocate_device_bars(pdev);
 	iseries_device_information(pdev, bus, *sub_bus);
 }
-- 
1.6.0.6

^ permalink raw reply related

* [PATCH v4] powerpc: Keep track of emulated instructions
From: Geert Uytterhoeven @ 2009-05-18 12:10 UTC (permalink / raw)
  To: Linux/PPC Development

If CONFIG_PPC_EMULATED_STATS is enabled, make available counters for the
various classes of emulated instructions under
/sys/kernel/debug/powerpc/emulated_instructions/ (assumed debugfs is mounted on
/sys/kernel/debug).  Optionally (controlled by
/sys/kernel/debug/powerpc/emulated_instructions/do_warn), rate-limited warnings
can be printed to the console when instructions are emulated.

Signed-off-by: Geert Uytterhoeven <Geert.Uytterhoeven@sonycom.com>
---
Tested on ppc64 (ps3) and ppc32 (sequoia) using mfpvr.

v4:
  - Introduce CONFIG_PPC_EMULATED_STATS

v3:
  - Add generic unaligned
  - Switch from sysfs + sysctl to debugfs. All virtual files now show up under
    /sys/kernel/debug/powerpc/emulated_instructions (assumed debugfs mounted on
    /sys/kernel/debug)
  - Enable the printing of rate-limited warnings by writing a non-zero value to
    /sys/kernel/debug/powerpc/emulated_instructions/do_warn
  - Switch from per-CPU to system-wide counters
  - Always use 32-bit counters (was 64-bit on ppc64)
  - Counters are writable, i.e. can be reset (by root)

v2:
  - arch/powerpc/kernel/sysfs.c is now compiled on ppc32, so we can provide
    counters in sysfs on ppc32, too,
  - WARN_EMULATED() is a no-op if CONFIG_SYSCTL is disabled,
  - Add warnings for altivec,
  - Add warnings for recently introduced emulation of vsx and isel
    instructions.

 arch/powerpc/Kconfig.debug              |   13 ++++
 arch/powerpc/include/asm/emulated_ops.h |   73 +++++++++++++++++++++++
 arch/powerpc/kernel/align.c             |   20 +++++-
 arch/powerpc/kernel/traps.c             |   96 ++++++++++++++++++++++++++++++-
 4 files changed, 196 insertions(+), 6 deletions(-)
 create mode 100644 arch/powerpc/include/asm/emulated_ops.h

diff --git a/arch/powerpc/Kconfig.debug b/arch/powerpc/Kconfig.debug
index a1098e2..d79a902 100644
--- a/arch/powerpc/Kconfig.debug
+++ b/arch/powerpc/Kconfig.debug
@@ -41,6 +41,19 @@ config HCALL_STATS
 	  This option will add a small amount of overhead to all hypervisor
 	  calls.
 
+config PPC_EMULATED_STATS
+	bool "Emulated instructions tracking"
+	depends on DEBUG_FS
+	help
+	  Adds code to keep track of the number of instructions that are
+	  emulated by the in-kernel emulator. Counters for the various classes
+	  of emulated instructions are available under
+	  powerpc/emulated_instructions/ in the root of the debugfs file
+	  system. Optionally (controlled by
+	  powerpc/emulated_instructions/do_warn in debugfs), rate-limited
+	  warnings can be printed to the console when instructions are
+	  emulated.
+
 config CODE_PATCHING_SELFTEST
 	bool "Run self-tests of the code-patching code."
 	depends on DEBUG_KERNEL
diff --git a/arch/powerpc/include/asm/emulated_ops.h b/arch/powerpc/include/asm/emulated_ops.h
new file mode 100644
index 0000000..9154e85
--- /dev/null
+++ b/arch/powerpc/include/asm/emulated_ops.h
@@ -0,0 +1,73 @@
+/*
+ *  Copyright 2007 Sony Corporation
+ *
+ *  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; version 2 of the License.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _ASM_POWERPC_EMULATED_OPS_H
+#define _ASM_POWERPC_EMULATED_OPS_H
+
+#include <asm/atomic.h>
+
+
+#ifdef CONFIG_PPC_EMULATED_STATS
+
+struct ppc_emulated_entry {
+	const char *name;
+	atomic_t val;
+};
+
+extern struct ppc_emulated {
+#ifdef CONFIG_ALTIVEC
+	struct ppc_emulated_entry altivec;
+#endif
+	struct ppc_emulated_entry dcba;
+	struct ppc_emulated_entry dcbz;
+	struct ppc_emulated_entry fp_pair;
+	struct ppc_emulated_entry isel;
+	struct ppc_emulated_entry mcrxr;
+	struct ppc_emulated_entry mfpvr;
+	struct ppc_emulated_entry multiple;
+	struct ppc_emulated_entry popcntb;
+	struct ppc_emulated_entry spe;
+	struct ppc_emulated_entry string;
+	struct ppc_emulated_entry unaligned;
+#ifdef CONFIG_MATH_EMULATION
+	struct ppc_emulated_entry math;
+#elif defined(CONFIG_8XX_MINIMAL_FPEMU)
+	struct ppc_emulated_entry 8xx;
+#endif
+#ifdef CONFIG_VSX
+	struct ppc_emulated_entry vsx;
+#endif
+} ppc_emulated;
+
+extern u32 ppc_warn_emulated;
+
+extern void ppc_warn_emulated_print(const char *type);
+
+#define PPC_WARN_EMULATED(type)						 \
+	do {								 \
+		atomic_inc(&ppc_emulated.type.val);			 \
+		if (ppc_warn_emulated)					 \
+			ppc_warn_emulated_print(ppc_emulated.type.name); \
+	} while (0)
+
+#else /* !CONFIG_PPC_EMULATED_STATS */
+
+#define PPC_WARN_EMULATED(type)	do { } while (0)
+
+#endif /* !CONFIG_PPC_EMULATED_STATS */
+
+#endif /* _ASM_POWERPC_EMULATED_OPS_H */
diff --git a/arch/powerpc/kernel/align.c b/arch/powerpc/kernel/align.c
index 5ffcfaa..a5b632e 100644
--- a/arch/powerpc/kernel/align.c
+++ b/arch/powerpc/kernel/align.c
@@ -24,6 +24,7 @@
 #include <asm/system.h>
 #include <asm/cache.h>
 #include <asm/cputable.h>
+#include <asm/emulated_ops.h>
 
 struct aligninfo {
 	unsigned char len;
@@ -730,8 +731,10 @@ int fix_alignment(struct pt_regs *regs)
 	areg = dsisr & 0x1f;		/* register to update */
 
 #ifdef CONFIG_SPE
-	if ((instr >> 26) == 0x4)
+	if ((instr >> 26) == 0x4) {
+		PPC_WARN_EMULATED(spe);
 		return emulate_spe(regs, reg, instr);
+	}
 #endif
 
 	instr = (dsisr >> 10) & 0x7f;
@@ -783,23 +786,28 @@ int fix_alignment(struct pt_regs *regs)
 			flags |= SPLT;
 			nb = 8;
 		}
+		PPC_WARN_EMULATED(vsx);
 		return emulate_vsx(addr, reg, areg, regs, flags, nb);
 	}
 #endif
 	/* A size of 0 indicates an instruction we don't support, with
 	 * the exception of DCBZ which is handled as a special case here
 	 */
-	if (instr == DCBZ)
+	if (instr == DCBZ) {
+		PPC_WARN_EMULATED(dcbz);
 		return emulate_dcbz(regs, addr);
+	}
 	if (unlikely(nb == 0))
 		return 0;
 
 	/* Load/Store Multiple instructions are handled in their own
 	 * function
 	 */
-	if (flags & M)
+	if (flags & M) {
+		PPC_WARN_EMULATED(multiple);
 		return emulate_multiple(regs, addr, reg, nb,
 					flags, instr, swiz);
+	}
 
 	/* Verify the address of the operand */
 	if (unlikely(user_mode(regs) &&
@@ -816,8 +824,12 @@ int fix_alignment(struct pt_regs *regs)
 	}
 
 	/* Special case for 16-byte FP loads and stores */
-	if (nb == 16)
+	if (nb == 16) {
+		PPC_WARN_EMULATED(fp_pair);
 		return emulate_fp_pair(addr, reg, flags);
+	}
+
+	PPC_WARN_EMULATED(unaligned);
 
 	/* If we are loading, get the data from user space, else
 	 * get it from register values
diff --git a/arch/powerpc/kernel/traps.c b/arch/powerpc/kernel/traps.c
index 678fbff..6a5b2b7 100644
--- a/arch/powerpc/kernel/traps.c
+++ b/arch/powerpc/kernel/traps.c
@@ -33,7 +33,9 @@
 #include <linux/backlight.h>
 #include <linux/bug.h>
 #include <linux/kdebug.h>
+#include <linux/debugfs.h>
 
+#include <asm/emulated_ops.h>
 #include <asm/pgtable.h>
 #include <asm/uaccess.h>
 #include <asm/system.h>
@@ -757,36 +759,44 @@ static int emulate_instruction(struct pt_regs *regs)
 
 	/* Emulate the mfspr rD, PVR. */
 	if ((instword & PPC_INST_MFSPR_PVR_MASK) == PPC_INST_MFSPR_PVR) {
+		PPC_WARN_EMULATED(mfpvr);
 		rd = (instword >> 21) & 0x1f;
 		regs->gpr[rd] = mfspr(SPRN_PVR);
 		return 0;
 	}
 
 	/* Emulating the dcba insn is just a no-op.  */
-	if ((instword & PPC_INST_DCBA_MASK) == PPC_INST_DCBA)
+	if ((instword & PPC_INST_DCBA_MASK) == PPC_INST_DCBA) {
+		PPC_WARN_EMULATED(dcba);
 		return 0;
+	}
 
 	/* Emulate the mcrxr insn.  */
 	if ((instword & PPC_INST_MCRXR_MASK) == PPC_INST_MCRXR) {
 		int shift = (instword >> 21) & 0x1c;
 		unsigned long msk = 0xf0000000UL >> shift;
 
+		PPC_WARN_EMULATED(mcrxr);
 		regs->ccr = (regs->ccr & ~msk) | ((regs->xer >> shift) & msk);
 		regs->xer &= ~0xf0000000UL;
 		return 0;
 	}
 
 	/* Emulate load/store string insn. */
-	if ((instword & PPC_INST_STRING_GEN_MASK) == PPC_INST_STRING)
+	if ((instword & PPC_INST_STRING_GEN_MASK) == PPC_INST_STRING) {
+		PPC_WARN_EMULATED(string);
 		return emulate_string_inst(regs, instword);
+	}
 
 	/* Emulate the popcntb (Population Count Bytes) instruction. */
 	if ((instword & PPC_INST_POPCNTB_MASK) == PPC_INST_POPCNTB) {
+		PPC_WARN_EMULATED(popcntb);
 		return emulate_popcntb_inst(regs, instword);
 	}
 
 	/* Emulate isel (Integer Select) instruction */
 	if ((instword & PPC_INST_ISEL_MASK) == PPC_INST_ISEL) {
+		PPC_WARN_EMULATED(isel);
 		return emulate_isel(regs, instword);
 	}
 
@@ -984,6 +994,8 @@ void SoftwareEmulation(struct pt_regs *regs)
 
 #ifdef CONFIG_MATH_EMULATION
 	errcode = do_mathemu(regs);
+	if (errcode >= 0)
+		PPC_WARN_EMULATED(math);
 
 	switch (errcode) {
 	case 0:
@@ -1005,6 +1017,9 @@ void SoftwareEmulation(struct pt_regs *regs)
 
 #elif defined(CONFIG_8XX_MINIMAL_FPEMU)
 	errcode = Soft_emulate_8xx(regs);
+	if (errcode >= 0)
+		PPC_WARN_EMULATED(8xx);
+
 	switch (errcode) {
 	case 0:
 		emulate_single_step(regs);
@@ -1088,6 +1103,7 @@ void altivec_assist_exception(struct pt_regs *regs)
 
 	flush_altivec_to_thread(current);
 
+	PPC_WARN_EMULATED(altivec);
 	err = emulate_altivec(regs);
 	if (err == 0) {
 		regs->nip += 4;		/* skip emulated instruction */
@@ -1286,3 +1302,79 @@ void kernel_bad_stack(struct pt_regs *regs)
 void __init trap_init(void)
 {
 }
+
+
+#ifdef CONFIG_PPC_EMULATED_STATS
+
+#define WARN_EMULATED_SETUP(type)	.type = { .name = #type }
+
+struct ppc_emulated ppc_emulated = {
+#ifdef CONFIG_ALTIVEC
+	WARN_EMULATED_SETUP(altivec),
+#endif
+	WARN_EMULATED_SETUP(dcba),
+	WARN_EMULATED_SETUP(dcbz),
+	WARN_EMULATED_SETUP(fp_pair),
+	WARN_EMULATED_SETUP(isel),
+	WARN_EMULATED_SETUP(mcrxr),
+	WARN_EMULATED_SETUP(mfpvr),
+	WARN_EMULATED_SETUP(multiple),
+	WARN_EMULATED_SETUP(popcntb),
+	WARN_EMULATED_SETUP(spe),
+	WARN_EMULATED_SETUP(string),
+	WARN_EMULATED_SETUP(unaligned),
+#ifdef CONFIG_MATH_EMULATION
+	WARN_EMULATED_SETUP(math),
+#elif defined(CONFIG_8XX_MINIMAL_FPEMU)
+	WARN_EMULATED_SETUP(8xx),
+#endif
+#ifdef CONFIG_VSX
+	WARN_EMULATED_SETUP(vsx),
+#endif
+};
+
+u32 ppc_warn_emulated;
+
+void ppc_warn_emulated_print(const char *type)
+{
+	if (printk_ratelimit())
+		pr_warning("%s used emulated %s instruction\n", current->comm,
+			   type);
+}
+
+static int __init ppc_warn_emulated_init(void)
+{
+	struct dentry *dir, *d;
+	unsigned int i;
+	struct ppc_emulated_entry *entries = (void *)&ppc_emulated;
+
+	if (!powerpc_debugfs_root)
+		return -ENODEV;
+
+	dir = debugfs_create_dir("emulated_instructions",
+				 powerpc_debugfs_root);
+	if (!dir)
+		return -ENOMEM;
+
+	d = debugfs_create_u32("do_warn", S_IRUGO | S_IWUSR, dir,
+			       &ppc_warn_emulated);
+	if (!d)
+		goto fail;
+
+	for (i = 0; i < sizeof(ppc_emulated)/sizeof(*entries); i++) {
+		d = debugfs_create_u32(entries[i].name, S_IRUGO | S_IWUSR, dir,
+				       (u32 *)&entries[i].val.counter);
+		if (!d)
+			goto fail;
+	}
+
+	return 0;
+
+fail:
+	debugfs_remove_recursive(dir);
+	return -ENOMEM;
+}
+
+device_initcall(ppc_warn_emulated_init);
+
+#endif /* CONFIG_PPC_EMULATED_STATS */
-- 
1.6.2.4

With kind regards,

Geert Uytterhoeven
Software Architect
Techsoft Centre

Technology and Software Centre Europe
The Corporate Village · Da Vincilaan 7-D1 · B-1935 Zaventem · Belgium

Phone:    +32 (0)2 700 8453
Fax:      +32 (0)2 700 8622
E-mail:   Geert.Uytterhoeven@sonycom.com
Internet: http://www.sony-europe.com/

A division of Sony Europe (Belgium) N.V.
VAT BE 0413.825.160 · RPR Brussels
Fortis · BIC GEBABEBB · IBAN BE41293037680010

^ permalink raw reply related

* Re: [PATCH] i2c-mpc: generate START condition after STOP caused by read i2c_msg
From: Wolfram Sang @ 2009-05-18 11:04 UTC (permalink / raw)
  To: Esben Haabendal; +Cc: linuxppc-dev, linux-i2c
In-Reply-To: <d2b9ea600905150552u6951a92es5dd119dcc37e641d@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 497 bytes --]

> Could we go forward with my initial patch, and then continue the work
> on this repeated START approach for future releases?

Then could you please send another version of your patch with the CodingStyle
issue removed and an updated description what this patch fixes and what still
needs to be resolved?

Regards,

   Wolfram

-- 
Pengutronix e.K.                           | Wolfram Sang                |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply

* [v1 PATCH 0/4] Add INT mode support for EDAC drivers on Maple
From: Harry Ciao @ 2009-05-18 10:04 UTC (permalink / raw)
  To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel


Hi Ben,

This is the v2 patches that have integrated your suggestions to
remove the refcount for a hwriq2virq mapping as long as we don't
dispose it, the changes are mostly within the 1/4 patch where the
unnecessary refcount and irqmap structure are removed, and callings
to edac_put_mpic_irq() are also removed from the rest of patches.

Since there are 3 EDAC modules on Maple will use one same copy of
code to create hwirq2virq mappings, I perfer to preserve it in
edac_mpic_irq.c.

How do feel about the assumption that MPIC will latch INT 0 pin for 
the NMI Reqeust Messages whose vector is == 0? This is the thing that
I have least confidence in, so far all I can get is the brief
introduction in CPC925 user manual that "This interrupt vector is used
to set a corresponding interrupt latch", P111, so I think it imples that
vector==0 will latch pin #0.

Many thanks for all your comments!

Best regards,

Harry

^ permalink raw reply

* [v1 PATCH 1/4] EDAC: MPIC Hypertransport IRQ support
From: Harry Ciao @ 2009-05-18 10:04 UTC (permalink / raw)
  To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel
In-Reply-To: <1242641100-15324-1-git-send-email-qingtao.cao@windriver.com>

Collect the machine specific code that creates the hwirq2virq mapping 
for the possible multiple EDAC modules on the same machine that supports
MPIC.

Multiple calling of irq_create_of_mapping() for the same hwirq will
always return the same virq, since the mapping won't be refcounted so
we just don't call irq_dispose_mapping() against it. This won't occupy
unnecessary resource since irq_map[] is of fixed size(==512).

The edac_mpic_irq.c is inert for EDAC drivers where related hardware
is not connecting to MPIC, so it should be controlled by CONFIG_MPIC.

Signed-off-by: Harry Ciao <qingtao.cao@windriver.com>
---
 drivers/edac/Makefile        |    4 ++
 drivers/edac/edac_mpic_irq.c |   86 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/edac.h         |   20 ++++++++++
 3 files changed, 110 insertions(+), 0 deletions(-)
 create mode 100644 drivers/edac/edac_mpic_irq.c

diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile
index 07a31cf..62778ee 100644
--- a/drivers/edac/Makefile
+++ b/drivers/edac/Makefile
@@ -17,6 +17,10 @@ ifdef CONFIG_PCI
 edac_core-objs	+= edac_pci.o edac_pci_sysfs.o
 endif
 
+ifdef CONFIG_MPIC
+edac_core-objs += edac_mpic_irq.o
+endif
+
 obj-$(CONFIG_EDAC_AMD76X)		+= amd76x_edac.o
 obj-$(CONFIG_EDAC_CPC925)		+= cpc925_edac.o
 obj-$(CONFIG_EDAC_I5000)		+= i5000_edac.o
diff --git a/drivers/edac/edac_mpic_irq.c b/drivers/edac/edac_mpic_irq.c
new file mode 100644
index 0000000..7486b15
--- /dev/null
+++ b/drivers/edac/edac_mpic_irq.c
@@ -0,0 +1,86 @@
+/*
+ * edac_mpic_irq.c -
+ * 	Collect the machine specific code that creates the hwirq2virq
+ * 	mapping for the possible multiple EDAC modules on the same
+ * 	machine that supports MPIC.
+ *
+ * Copyright (c) 2009 Wind River Systems, Inc.
+ *
+ * Authors:	Cao Qingtao <qingtao.cao@windriver.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/edac.h>
+
+#ifdef CONFIG_PPC_MAPLE
+static int edac_maple_get_irq(int hwirq)
+{
+	struct device_node *np, *mpic_node = NULL;
+	int irq = NO_IRQ;
+
+	/*
+	 * Locate MPIC in the device-tree. Note that there is a bug
+	 * in Maple device-tree where the type of the controller is
+	 * open-pic and not interrupt-controller
+	 */
+	for_each_node_by_type(np, "interrupt-controller") {
+		if (of_device_is_compatible(np, "open-pic")) {
+			mpic_node = np;
+			break;
+		}
+	}
+
+	if (mpic_node == NULL) {
+		for_each_node_by_type(np, "open-pic") {
+			mpic_node = np;
+			break;
+		}
+	}
+
+	if (mpic_node) {
+		irq = irq_create_of_mapping(mpic_node, &hwirq, 1);
+		of_node_put(mpic_node);
+	} else
+		printk(KERN_ERR "Failed to locate the MPIC DTB node\n");
+
+	return irq;
+}
+#endif
+
+/*
+ * NOTE:
+ * The EDAC driver should implement and register its machine-specific
+ * method to get a virtual IRQ here.
+ */
+int edac_get_mpic_irq(int hwirq)
+{
+	int virq = NO_IRQ;
+
+	if ((hwirq != MPIC_HWIRQ_HT_NMI) &&
+	    (hwirq != MPIC_HWIRQ_INTERNAL_ERROR))
+		return NO_IRQ;
+
+#ifdef CONFIG_PPC_MAPLE
+	virq = edac_maple_get_irq(hwirq);
+#endif
+
+	return virq;
+
+}
+EXPORT_SYMBOL_GPL(edac_get_mpic_irq);
diff --git a/include/linux/edac.h b/include/linux/edac.h
index 7cf92e8..c122d22 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -38,4 +38,24 @@ static inline void opstate_init(void)
 	return;
 }
 
+#ifdef CONFIG_MPIC
+enum {
+	/*
+	 * Vector carried in southbridge NMI Request Messages
+	 * posted through Hypertransport Channel
+	 */
+	MPIC_HWIRQ_HT_NMI = 0,
+
+	/*
+	 * Vector for MPIC Internal Error
+	 */
+	MPIC_HWIRQ_INTERNAL_ERROR = 2,
+
+	MPIC_HWIRQS,	/* must be the very last */
+};
+
+/* Create a hwirq2virq mapping for the specified hwirq */
+extern int edac_get_mpic_irq(int hwirq);
+#endif
+
 #endif
-- 
1.5.6.2

^ permalink raw reply related

* [v1 PATCH 4/4] EDAC: INT mode support for AMD8131 driver
From: Harry Ciao @ 2009-05-18 10:05 UTC (permalink / raw)
  To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel
In-Reply-To: <1242641100-15324-4-git-send-email-qingtao.cao@windriver.com>

Support EDAC INT mode for AMD8131 EDAC driver, which may post upstream
NMI interrupt request messages that will latch MPIC INT0 pin.

Following aspects for this patch have been tested:
1, module initialization and deletion for NMI mode;
2, creation and deletion for the mapping between hwirq==0 to a virq;

Note, due to the difficulty and complexity to generate a real hardware
EDAC Errors, below aspects have not been tested yet:
1, code that controls the generation of the NMI Request Message;
2, the mapping from the NMI Request Messages to MPIC INT0 pin;
3, if EDAC isr methods could handle errors correctly.

Signed-off-by: Harry Ciao <qingtao.cao@windriver.com>
---
 drivers/edac/amd8131_edac.c |  169 ++++++++++++++++++++++++++++++++++++++-----
 drivers/edac/amd8131_edac.h |   20 +++++-
 2 files changed, 170 insertions(+), 19 deletions(-)

diff --git a/drivers/edac/amd8131_edac.c b/drivers/edac/amd8131_edac.c
index b432d60..9b8217b 100644
--- a/drivers/edac/amd8131_edac.c
+++ b/drivers/edac/amd8131_edac.c
@@ -28,6 +28,7 @@
 #include <linux/bitops.h>
 #include <linux/edac.h>
 #include <linux/pci_ids.h>
+#include <linux/interrupt.h>
 
 #include "edac_core.h"
 #include "edac_module.h"
@@ -36,6 +37,11 @@
 #define AMD8131_EDAC_REVISION	" Ver: 1.0.0 " __DATE__
 #define AMD8131_EDAC_MOD_STR	"amd8131_edac"
 
+static int amd8131_op_state = EDAC_OPSTATE_POLL;
+module_param(amd8131_op_state, int, 0444);
+MODULE_PARM_DESC(amd8131_op_state, "EDAC Error Reporting state: 0=Poll, 1=NMI");
+static int amd8131_nmi_irq;
+
 /* Wrapper functions for accessing PCI configuration space */
 static void edac_pci_read_dword(struct pci_dev *dev, int reg, u32 *val32)
 {
@@ -139,6 +145,17 @@ static void amd8131_pcix_init(struct amd8131_dev_info *dev_info)
 	edac_pci_read_dword(dev, REG_LNK_CTRL_B, &val32);
 	val32 |= LNK_CTRL_CRCFEN;
 	edac_pci_write_dword(dev, REG_LNK_CTRL_B, val32);
+
+	/* enable HT NMI messages generation on errors */
+	if (amd8131_op_state == EDAC_OPSTATE_NMI) {
+		edac_pci_read_dword(dev, REG_MISC_I, &val32);
+		val32 &= ~MISC_I_NIOAMODE;
+		edac_pci_write_dword(dev, REG_MISC_I, val32);
+
+		edac_pci_read_dword(dev, REG_MISC_II, &val32);
+		val32 |= MISC_II_NMIEN;
+		edac_pci_write_dword(dev, REG_MISC_II, val32);
+	}
 }
 
 static void amd8131_pcix_exit(struct amd8131_dev_info *dev_info)
@@ -165,6 +182,17 @@ static void amd8131_pcix_exit(struct amd8131_dev_info *dev_info)
 	edac_pci_read_dword(dev, REG_LNK_CTRL_B, &val32);
 	val32 &= ~LNK_CTRL_CRCFEN;
 	edac_pci_write_dword(dev, REG_LNK_CTRL_B, val32);
+
+	/* Disable HT NMI messages on errors*/
+	if (amd8131_op_state == EDAC_OPSTATE_NMI) {
+		edac_pci_read_dword(dev, REG_MISC_II, &val32);
+		val32 &= ~MISC_II_NMIEN;
+		edac_pci_write_dword(dev, REG_MISC_II, val32);
+
+		edac_pci_read_dword(dev, REG_MISC_I, &val32);
+		val32 |= MISC_I_NIOAMODE;
+		edac_pci_write_dword(dev, REG_MISC_I, val32);
+	}
 }
 
 static void amd8131_pcix_check(struct edac_pci_ctl_info *edac_dev)
@@ -233,12 +261,33 @@ static void amd8131_pcix_check(struct edac_pci_ctl_info *edac_dev)
 	}
 }
 
+static irqreturn_t amd8131_pcix_isr(int irq, void *dev_id)
+{
+	struct edac_pci_ctl_info *edac_pci = dev_id;
+	struct amd8131_dev_info *dev_info = edac_pci->pvt_info;
+	struct pci_dev *dev = dev_info->dev;
+	u32 val32;
+
+	/*
+	 * Only a handful of errors in PCI-X Bridge Memory Base-Limit
+	 * Register could trigger NMI interrupt request message.
+	 */
+	edac_pci_read_dword(dev, REG_MEM_LIM, &val32);
+	if (!(val32 & MEM_LIMIT_NMI_MASK))
+		return IRQ_NONE;
+
+	amd8131_pcix_check(edac_pci);
+
+	return IRQ_HANDLED;
+}
+
 static struct amd8131_info amd8131_chipset = {
 	.err_dev = PCI_DEVICE_ID_AMD_8131_APIC,
 	.devices = amd8131_devices,
 	.init = amd8131_pcix_init,
 	.exit = amd8131_pcix_exit,
 	.check = amd8131_pcix_check,
+	.isr = amd8131_pcix_isr,
 };
 
 /*
@@ -249,6 +298,7 @@ static struct amd8131_info amd8131_chipset = {
 static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
 {
 	struct amd8131_dev_info *dev_info;
+	int ret = -ENODEV;
 
 	for (dev_info = amd8131_chipset.devices; dev_info->inst != NO_BRIDGE;
 		dev_info++)
@@ -256,7 +306,7 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
 			break;
 
 	if (dev_info->inst == NO_BRIDGE) /* should never happen */
-		return -ENODEV;
+		goto out;
 
 	/*
 	 * We can't call pci_get_device() as we are used to do because
@@ -265,12 +315,11 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	dev_info->dev = pci_dev_get(dev);
 
 	if (pci_enable_device(dev_info->dev)) {
-		pci_dev_put(dev_info->dev);
 		printk(KERN_ERR "failed to enable:"
 			"vendor %x, device %x, devfn %x, name %s\n",
 			PCI_VENDOR_ID_AMD, amd8131_chipset.err_dev,
 			dev_info->devfn, dev_info->ctl_name);
-		return -ENODEV;
+		goto err1;
 	}
 
 	/*
@@ -280,8 +329,10 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	 */
 	dev_info->edac_idx = edac_pci_alloc_index();
 	dev_info->edac_dev = edac_pci_alloc_ctl_info(0, dev_info->ctl_name);
-	if (!dev_info->edac_dev)
-		return -ENOMEM;
+	if (!dev_info->edac_dev) {
+		ret = -ENOMEM;
+		goto err1;
+	}
 
 	dev_info->edac_dev->pvt_info = dev_info;
 	dev_info->edac_dev->dev = &dev_info->dev->dev;
@@ -289,7 +340,7 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	dev_info->edac_dev->ctl_name = dev_info->ctl_name;
 	dev_info->edac_dev->dev_name = dev_name(&dev_info->dev->dev);
 
-	if (edac_op_state == EDAC_OPSTATE_POLL)
+	if (amd8131_op_state == EDAC_OPSTATE_POLL)
 		dev_info->edac_dev->edac_check = amd8131_chipset.check;
 
 	if (amd8131_chipset.init)
@@ -298,8 +349,8 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	if (edac_pci_add_device(dev_info->edac_dev, dev_info->edac_idx) > 0) {
 		printk(KERN_ERR "failed edac_pci_add_device() for %s\n",
 			dev_info->ctl_name);
-		edac_pci_free_ctl_info(dev_info->edac_dev);
-		return -ENODEV;
+		ret = -ENOMEM;
+		goto err2;
 	}
 
 	printk(KERN_INFO "added one device on AMD8131 "
@@ -307,7 +358,18 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
 		PCI_VENDOR_ID_AMD, amd8131_chipset.err_dev,
 		dev_info->devfn, dev_info->ctl_name);
 
-	return 0;
+	ret = 0;
+	goto out;
+
+err2:
+	if (amd8131_chipset.exit)
+		amd8131_chipset.exit(dev_info);
+
+	edac_pci_free_ctl_info(dev_info->edac_dev);
+err1:
+	pci_dev_put(dev_info->dev);
+out:
+	return ret;
 }
 
 static void amd8131_remove(struct pci_dev *dev)
@@ -322,14 +384,14 @@ static void amd8131_remove(struct pci_dev *dev)
 	if (dev_info->inst == NO_BRIDGE) /* should never happen */
 		return;
 
+	if (amd8131_chipset.exit)
+		amd8131_chipset.exit(dev_info);
+
 	if (dev_info->edac_dev) {
 		edac_pci_del_device(dev_info->edac_dev->dev);
 		edac_pci_free_ctl_info(dev_info->edac_dev);
 	}
 
-	if (amd8131_chipset.exit)
-		amd8131_chipset.exit(dev_info);
-
 	pci_dev_put(dev_info->dev);
 }
 
@@ -342,9 +404,7 @@ static const struct pci_device_id amd8131_edac_pci_tbl[] = {
 	.class_mask = 0,
 	.driver_data = 0,
 	},
-	{
-	0,
-	}			/* table is NULL-terminated */
+	{0}	/* table is NULL-terminated */
 };
 MODULE_DEVICE_TABLE(pci, amd8131_edac_pci_tbl);
 
@@ -355,20 +415,93 @@ static struct pci_driver amd8131_edac_driver = {
 	.id_table = amd8131_edac_pci_tbl,
 };
 
+/*
+ * AMD8131 NMI handler - check PCI-X Bridges to claim any
+ * possible NMI instance.
+ * Southbridge NMI Request messages posted through Hypertransport
+ * Channel will be transferred to a MPIC interrupt instance.
+ *
+ * NOTE: According to AMD8131 data sheet 4.5.7 section,
+ * only a partial of error detections could generate NMI
+ * Upstream Hypertransport Interrupt request messages, so
+ * use NMI mode at sacrifice that not all error detections
+ * could be made use of.
+ */
+static irqreturn_t amd8131_nmi_handler(int irq, void *dev_id)
+{
+	struct amd8131_info *info = dev_id;
+	struct amd8131_dev_info *dev_info;
+	irqreturn_t ret = IRQ_NONE;
+
+	if (!info->isr)
+		return IRQ_NONE;
+
+	for (dev_info = info->devices; dev_info->inst != NO_BRIDGE; dev_info++)
+		ret |= info->isr(irq, dev_info->edac_dev);
+
+	return ret;
+}
+
+static void __init amd8131_nmi_handler_setup(void)
+{
+	int ret;
+
+	if (amd8131_op_state != EDAC_OPSTATE_NMI)
+		return;
+
+	amd8131_nmi_irq = NO_IRQ;
+
+#ifdef CONFIG_MPIC
+	amd8131_nmi_irq = edac_get_mpic_irq(MPIC_HWIRQ_HT_NMI);
+#endif
+
+	if (amd8131_nmi_irq == NO_IRQ) {
+		printk(KERN_ERR "%s: failed to get virq "
+			"for AMD8131 NMI requests\n", __func__);
+		return;
+	}
+
+	ret = request_irq(amd8131_nmi_irq, amd8131_nmi_handler,
+			  IRQF_SHARED, "[EDAC] AMD8131", &amd8131_chipset);
+	if (ret < 0) {
+		printk(KERN_INFO "%s: failed to request irq %d for "
+			"AMD8131 NMI requests\n", __func__, amd8131_nmi_irq);
+		return;
+	}
+
+	debugf0("%s: Successfully requested irq %d for AMD8131 NMI requests\n",
+		 __func__, amd8131_nmi_irq);
+}
+
+static void __exit amd8131_nmi_handler_exit(void)
+{
+	if (amd8131_op_state != EDAC_OPSTATE_NMI)
+		return;
+
+	if (amd8131_nmi_irq != NO_IRQ)
+		free_irq(amd8131_nmi_irq, &amd8131_chipset);
+}
+
 static int __init amd8131_edac_init(void)
 {
+	int ret;
+
 	printk(KERN_INFO "AMD8131 EDAC driver " AMD8131_EDAC_REVISION "\n");
 	printk(KERN_INFO "\t(c) 2008 Wind River Systems, Inc.\n");
 
-	/* Only POLL mode supported so far */
-	edac_op_state = EDAC_OPSTATE_POLL;
+	ret = pci_register_driver(&amd8131_edac_driver);
 
-	return pci_register_driver(&amd8131_edac_driver);
+	if (ret == 0)
+		amd8131_nmi_handler_setup();
+
+	return ret;
 }
 
 static void __exit amd8131_edac_exit(void)
 {
 	pci_unregister_driver(&amd8131_edac_driver);
+
+	amd8131_nmi_handler_exit();
 }
 
 module_init(amd8131_edac_init);
diff --git a/drivers/edac/amd8131_edac.h b/drivers/edac/amd8131_edac.h
index 60e0d1c..7e86cbf 100644
--- a/drivers/edac/amd8131_edac.h
+++ b/drivers/edac/amd8131_edac.h
@@ -61,7 +61,8 @@ enum mem_limit_bits {
 	MEM_LIMIT_STA	= BIT(27),
 	MEM_LIMIT_MDPE	= BIT(24),
 	MEM_LIMIT_MASK	= MEM_LIMIT_DPE|MEM_LIMIT_RSE|MEM_LIMIT_RMA|
-				MEM_LIMIT_RTA|MEM_LIMIT_STA|MEM_LIMIT_MDPE
+				MEM_LIMIT_RTA|MEM_LIMIT_STA|MEM_LIMIT_MDPE,
+	MEM_LIMIT_NMI_MASK = MEM_LIMIT_DPE | MEM_LIMIT_RSE
 };
 
 /************************************************************
@@ -80,6 +81,22 @@ enum lnk_ctrl_bits {
 	LNK_CTRL_CRCFEN		= BIT(1)
 };
 
+/************************************************************
+ *	PCI-X Miscellaneous Register, Dev[B,A]:0x40
+ ************************************************************/
+#define REG_MISC_I	0x40
+enum misc_i_bits {
+	MISC_I_NIOAMODE	= BIT(0),
+};
+
+/************************************************************
+ *	PCI-X Miscellaneous II Register, Dev[B,A]:0x44
+ ************************************************************/
+#define REG_MISC_II	0x44
+enum misc_ii_bits {
+	MISC_II_NMIEN	= BIT(0),
+};
+
 enum pcix_bridge_inst {
 	NORTH_A = 0,
 	NORTH_B = 1,
@@ -113,6 +130,7 @@ struct amd8131_info {
 	void (*init)(struct amd8131_dev_info *dev_info);
 	void (*exit)(struct amd8131_dev_info *dev_info);
 	void (*check)(struct edac_pci_ctl_info *edac_dev);
+	irqreturn_t (*isr)(int irq, void *dev_id);
 };
 
 #endif /* _AMD8131_EDAC_H_ */
-- 
1.5.6.2

^ permalink raw reply related

* [v1 PATCH 3/4] EDAC: INT mode support for AMD8111 driver
From: Harry Ciao @ 2009-05-18 10:04 UTC (permalink / raw)
  To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel
In-Reply-To: <1242641100-15324-3-git-send-email-qingtao.cao@windriver.com>

Support EDAC INT mode for AMD8111 EDAC driver, which may post upstream
NMI interrupt request messages that will latch MPIC INT0 pin.

Following aspects for this patch have been tested:
1, module initialization and deletion for NMI mode;
2, creation and deletion for the mapping between hwirq==0 to a virq;

Note, due to the difficulty and complexity to generate a real hardware
EDAC Errors, below aspects have not been tested yet:
1, code that controls the generation of the NMI Request Message;
2, the mapping from the NMI Request Messages to MPIC INT0 pin;
3, if EDAC isr methods could handle errors correctly.

Signed-off-by: Harry Ciao <qingtao.cao@windriver.com>
---
 drivers/edac/amd8111_edac.c |  348 +++++++++++++++++++++++++++++++++++++------
 drivers/edac/amd8111_edac.h |   43 +++++-
 2 files changed, 343 insertions(+), 48 deletions(-)

diff --git a/drivers/edac/amd8111_edac.c b/drivers/edac/amd8111_edac.c
index 35b78d0..10dc8ac 100644
--- a/drivers/edac/amd8111_edac.c
+++ b/drivers/edac/amd8111_edac.c
@@ -38,6 +38,11 @@
 
 #define PCI_DEVICE_ID_AMD_8111_PCI	0x7460
 
+static int amd8111_op_state = EDAC_OPSTATE_POLL;
+module_param(amd8111_op_state, int, 0444);
+MODULE_PARM_DESC(amd8111_op_state, "EDAC Error Reporting state: 0=Poll, 1=NMI");
+static int amd8111_nmi_irq;
+
 enum amd8111_edac_devs {
 	LPC_BRIDGE = 0,
 };
@@ -89,10 +94,9 @@ static void edac_pci_write_byte(struct pci_dev *dev, int reg, u8 val8)
 			" PCI Access Write Error at 0x%x\n", reg);
 }
 
+/* device specific methods for AMD8111 PCI Bridge device */
 /*
- * device-specific methods for amd8111 PCI Bridge Controller
- *
- * Error Reporting and Handling for amd8111 chipset could be found
+ * Error Reporting and Handling for AMD8111 chipset could be found
  * in its datasheet 3.1.2 section, P37
  */
 static void amd8111_pci_bridge_init(struct amd8111_pci_info *pci_info)
@@ -125,7 +129,7 @@ static void amd8111_pci_bridge_init(struct amd8111_pci_info *pci_info)
 		edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32);
 
 	/* Last enable error detections */
-	if (edac_op_state == EDAC_OPSTATE_POLL) {
+	if (amd8111_op_state == EDAC_OPSTATE_POLL) {
 		/* Enable System Error reporting in global status register */
 		edac_pci_read_dword(dev, REG_PCI_STSCMD, &val32);
 		val32 |= PCI_STSCMD_SERREN;
@@ -140,6 +144,11 @@ static void amd8111_pci_bridge_init(struct amd8111_pci_info *pci_info)
 		edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32);
 		val32 |= PCI_INTBRG_CTRL_POLL_MASK;
 		edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32);
+	} else if (amd8111_op_state == EDAC_OPSTATE_NMI) {
+		/* Enable Parity Error detection on secondary PCI bus */
+		edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32);
+		val32 |= PCI_INTBRG_CTRL_PEREN;
+		edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32);
 	}
 }
 
@@ -148,7 +157,7 @@ static void amd8111_pci_bridge_exit(struct amd8111_pci_info *pci_info)
 	u32 val32;
 	struct pci_dev *dev = pci_info->dev;
 
-	if (edac_op_state == EDAC_OPSTATE_POLL) {
+	if (amd8111_op_state == EDAC_OPSTATE_POLL) {
 		/* Disable System Error reporting */
 		edac_pci_read_dword(dev, REG_PCI_STSCMD, &val32);
 		val32 &= ~PCI_STSCMD_SERREN;
@@ -163,6 +172,11 @@ static void amd8111_pci_bridge_exit(struct amd8111_pci_info *pci_info)
 		edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32);
 		val32 &= ~PCI_INTBRG_CTRL_POLL_MASK;
 		edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32);
+	} else if (amd8111_op_state == EDAC_OPSTATE_NMI) {
+		/* Disable Parity Error detection on secondary PCI bus */
+		edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32);
+		val32 &= ~PCI_INTBRG_CTRL_PEREN;
+		edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32);
 	}
 }
 
@@ -238,11 +252,136 @@ static void amd8111_pci_bridge_check(struct edac_pci_ctl_info *edac_dev)
 	}
 }
 
+static irqreturn_t amd8111_pci_bridge_isr(int irq, void *dev_id)
+{
+	struct edac_pci_ctl_info *edac_dev = dev_id;
+	struct amd8111_pci_info *pci_info = edac_dev->pvt_info;
+	struct pci_dev *dev = pci_info->dev;
+	u32 stscmd, htlink, intbrg, memlim;
+
+	edac_pci_read_dword(dev, REG_PCI_STSCMD, &stscmd);
+	edac_pci_read_dword(dev, REG_HT_LINK, &htlink);
+	edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &intbrg);
+	edac_pci_read_dword(dev, REG_MEM_LIM, &memlim);
+
+	if (!((stscmd & PCI_STSCMD_NMI_MASK) ||
+	      (htlink & HT_LINK_CRCERR) ||
+	      (intbrg & PCI_INTBRG_CTRL_DTSTAT) ||
+	      (memlim & MEM_LIMIT_CLEAR_MASK)))
+		return IRQ_NONE;
+
+	amd8111_pci_bridge_check(edac_dev);
+
+	return IRQ_HANDLED;
+}
+
+/* device specific methods for AMD8111 LPC Bridge device */
+/*
+ * According to AMD8111 datasheet 3.4.2.4 section, NMI is controlled
+ * by following equation:
+ * NMI = ~PORT70[NMIDIS] &
+ * 	(PM48[NMI_NOW] | ~PM48[NMI2SMI_EN] &
+ *	 (PORT61[SERR] & ~PORT61[CLRSERR]
+ *	 | PORT61[IOCHK] & ~PORT61[CLRIOCHK]
+ *	 | DevB:0x40[NMIONERR] & [status bits described in section 3.1.2]
+ *	 | DevA:0x1C[MDPE] & DevA:0x3C[PEREN]));
+ *
+ * PORT70[NMIDIS] and PM48[NMI2SMI_EN] will be turned off here
+ * if necessary, the rest of device-specific NMI control bits
+ * will be set separately.
+ */
+static int amd8111_NMI_global_enable(struct pci_dev *lpc_dev)
+{
+	struct pci_dev *dev = lpc_dev;
+	u8 val8;
+	u16 val16;
+	u32 val32, mapbase;
+	void __iomem *mmio_vbase;
+
+	/*
+	 * Global NMI disablement status could be read from
+	 * DevB:0x41[NMIDIS], clear PORT70[NMIDIS] only when
+	 * DevB:0x41[NMIDIS] is set.
+	 */
+	edac_pci_read_byte(dev, REG_IO_CTRL_2, &val8);
+	if (val8 & IO_CTRL_2_NMIDIS) {
+		val8 = __do_inb(REG_RTC);
+		val8 &= ~RTC_NMIDIS;
+		__do_outb(val8, REG_RTC);
+	}
+
+	/*
+	 * The start address of the 256-byte relocatable System Management
+	 * I/O register block is specified by DevB:3x58[PMBASE], and
+	 * accessing this MMIO region is controlled by DevB:3x41[PMIOEN].
+	 */
+	dev = pci_get_device(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS,
+			     NULL);
+	if (!dev) {
+		printk(KERN_ERR "%s: AMD8111 NMI control device not found: "
+			"vendor %x, device %x\n", __func__,
+			PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS);
+		return -ENODEV;
+	}
+
+	if (pci_enable_device(dev)) {
+		pci_dev_put(dev);
+		printk(KERN_ERR "%s: failed to enable: "
+			"vendor %x, device %x\n", __func__,
+			PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS);
+		return -ENODEV;
+	}
+
+	edac_pci_read_byte(dev, REG_GEN_CONFIG_2, &val8);
+	if (!(val8 & GEN_CONFIG_2_PMIOEN)) {
+		val8 |= GEN_CONFIG_2_PMIOEN;
+		edac_pci_write_byte(dev, REG_GEN_CONFIG_2, val8);
+	}
+
+	/*
+	 * get the physical address of the relocatable 256-byte
+	 * System Management I/O register block.
+	 */
+	edac_pci_read_dword(dev, REG_SYSMAN_IO_SPACE, &val32);
+	mapbase = val32 & SYSMAN_IO_SPACE_PMBASE_MASK;
+	mapbase += dev->bus->resource[0]->start;
+
+	if (!request_mem_region(mapbase, AMD8111_SYSMAN_IO_SIZE,
+				"amd8111_PMxx")) {
+		pci_dev_put(dev);
+		printk(KERN_ERR "%s: failed to request region\n", __func__);
+		return -EBUSY;
+	}
+
+	mmio_vbase = ioremap(mapbase, AMD8111_SYSMAN_IO_SIZE);
+	if (!mmio_vbase) {
+		printk(KERN_ERR "%s: failed to ioremap region: "
+			"address 0x%x, len 0x%x\n", __func__,
+			mapbase, AMD8111_SYSMAN_IO_SIZE);
+		pci_dev_put(dev);
+		return -ENOMEM;
+	}
+
+	/* clear PM48[NMI2SMI_EN] if necessary */
+	val16 = in_le16(mmio_vbase + IO_TCO_CTRL_1);
+	if (val16 & IO_TCO_CTRL_1_NMI2SMI_EN) {
+		val16 &= ~IO_TCO_CTRL_1_NMI2SMI_EN;
+		out_le16(mmio_vbase + IO_TCO_CTRL_1, val16);
+		printk(KERN_INFO "%s: PM48[NMI2SMI_EN] is cleared\n", __func__);
+	}
+
+	iounmap(mmio_vbase);
+	release_mem_region(mapbase, AMD8111_SYSMAN_IO_SIZE);
+
+	pci_dev_put(dev);
+
+	return 0;
+}
+
 static struct resource *legacy_io_res;
 static int at_compat_reg_broken;
 #define LEGACY_NR_PORTS	1
 
-/* device-specific methods for amd8111 LPC Bridge device */
 static void amd8111_lpc_bridge_init(struct amd8111_dev_info *dev_info)
 {
 	u8 val8;
@@ -278,10 +417,29 @@ static void amd8111_lpc_bridge_init(struct amd8111_dev_info *dev_info)
 	edac_pci_read_byte(dev, REG_IO_CTRL_1, &val8);
 	if (val8 & IO_CTRL_1_CLEAR_MASK)
 		edac_pci_write_byte(dev, REG_IO_CTRL_1, val8);
+
+	if (amd8111_op_state == EDAC_OPSTATE_NMI) {
+		/* Enable NMI generation on errors */
+		edac_pci_read_byte(dev, REG_IO_CTRL_1, &val8);
+		val8 |= IO_CTRL_1_NMIONERR;
+		edac_pci_write_byte(dev, REG_IO_CTRL_1, val8);
+
+		amd8111_NMI_global_enable(dev);
+	}
 }
 
 static void amd8111_lpc_bridge_exit(struct amd8111_dev_info *dev_info)
 {
+	u8 val8;
+	struct pci_dev *dev = dev_info->dev;
+
+	if (amd8111_op_state == EDAC_OPSTATE_NMI) {
+		/* Disable NMI generation on errors */
+		edac_pci_read_byte(dev, REG_IO_CTRL_1, &val8);
+		val8 &= ~IO_CTRL_1_NMIONERR;
+		edac_pci_write_byte(dev, REG_IO_CTRL_1, val8);
+	}
+
 	if (legacy_io_res)
 		release_region(REG_AT_COMPAT, LEGACY_NR_PORTS);
 }
@@ -322,6 +480,22 @@ static void amd8111_lpc_bridge_check(struct edac_device_ctl_info *edac_dev)
 	}
 }
 
+static irqreturn_t amd8111_lpc_bridge_isr(int irq, void *dev_id)
+{
+	struct edac_device_ctl_info *edac_dev = dev_id;
+	struct amd8111_dev_info *dev_info = edac_dev->pvt_info;
+	struct pci_dev *dev = dev_info->dev;
+	u8 val8;
+
+	edac_pci_read_byte(dev, REG_IO_CTRL_1, &val8);
+	if (!(val8 & IO_CTRL_1_CLEAR_MASK))
+		return IRQ_NONE;
+
+	amd8111_lpc_bridge_check(edac_dev);
+
+	return IRQ_HANDLED;
+}
+
 /* General devices represented by edac_device_ctl_info */
 static struct amd8111_dev_info amd8111_devices[] = {
 	[LPC_BRIDGE] = {
@@ -330,6 +504,7 @@ static struct amd8111_dev_info amd8111_devices[] = {
 		.init = amd8111_lpc_bridge_init,
 		.exit = amd8111_lpc_bridge_exit,
 		.check = amd8111_lpc_bridge_check,
+		.isr = amd8111_lpc_bridge_isr,
 	},
 	{0},
 };
@@ -342,6 +517,7 @@ static struct amd8111_pci_info amd8111_pcis[] = {
 		.init = amd8111_pci_bridge_init,
 		.exit = amd8111_pci_bridge_exit,
 		.check = amd8111_pci_bridge_check,
+		.isr = amd8111_pci_bridge_isr,
 	},
 	{0},
 };
@@ -350,25 +526,24 @@ static int amd8111_dev_probe(struct pci_dev *dev,
 				const struct pci_device_id *id)
 {
 	struct amd8111_dev_info *dev_info = &amd8111_devices[id->driver_data];
+	int ret = -ENODEV;
 
 	dev_info->dev = pci_get_device(PCI_VENDOR_ID_AMD,
 					dev_info->err_dev, NULL);
-
 	if (!dev_info->dev) {
 		printk(KERN_ERR "EDAC device not found:"
 			"vendor %x, device %x, name %s\n",
 			PCI_VENDOR_ID_AMD, dev_info->err_dev,
 			dev_info->ctl_name);
-		return -ENODEV;
+		goto out;
 	}
 
 	if (pci_enable_device(dev_info->dev)) {
-		pci_dev_put(dev_info->dev);
 		printk(KERN_ERR "failed to enable:"
 			"vendor %x, device %x, name %s\n",
 			PCI_VENDOR_ID_AMD, dev_info->err_dev,
 			dev_info->ctl_name);
-		return -ENODEV;
+		goto err1;
 	}
 
 	/*
@@ -381,8 +556,10 @@ static int amd8111_dev_probe(struct pci_dev *dev,
 		edac_device_alloc_ctl_info(0, dev_info->ctl_name, 1,
 					   NULL, 0, 0,
 					   NULL, 0, dev_info->edac_idx);
-	if (!dev_info->edac_dev)
-		return -ENOMEM;
+	if (!dev_info->edac_dev) {
+		ret = -ENOMEM;
+		goto err1;
+	}
 
 	dev_info->edac_dev->pvt_info = dev_info;
 	dev_info->edac_dev->dev = &dev_info->dev->dev;
@@ -390,7 +567,7 @@ static int amd8111_dev_probe(struct pci_dev *dev,
 	dev_info->edac_dev->ctl_name = dev_info->ctl_name;
 	dev_info->edac_dev->dev_name = dev_name(&dev_info->dev->dev);
 
-	if (edac_op_state == EDAC_OPSTATE_POLL)
+	if (amd8111_op_state == EDAC_OPSTATE_POLL)
 		dev_info->edac_dev->edac_check = dev_info->check;
 
 	if (dev_info->init)
@@ -399,16 +576,27 @@ static int amd8111_dev_probe(struct pci_dev *dev,
 	if (edac_device_add_device(dev_info->edac_dev) > 0) {
 		printk(KERN_ERR "failed to add edac_dev for %s\n",
 			dev_info->ctl_name);
-		edac_device_free_ctl_info(dev_info->edac_dev);
-		return -ENODEV;
+		ret = -ENOMEM;
+		goto err2;
 	}
 
-	printk(KERN_INFO "added one edac_dev on AMD8111 "
+	printk(KERN_INFO "added one device on AMD8111 "
 		"vendor %x, device %x, name %s\n",
 		PCI_VENDOR_ID_AMD, dev_info->err_dev,
 		dev_info->ctl_name);
 
-	return 0;
+	ret = 0;
+	goto out;
+
+err2:
+	if (dev_info->exit)
+		dev_info->exit(dev_info);
+
+	edac_device_free_ctl_info(dev_info->edac_dev);
+err1:
+	pci_dev_put(dev_info->dev);
+out:
+	return ret;
 }
 
 static void amd8111_dev_remove(struct pci_dev *dev)
@@ -422,14 +610,14 @@ static void amd8111_dev_remove(struct pci_dev *dev)
 	if (!dev_info->err_dev)	/* should never happen */
 		return;
 
+	if (dev_info->exit)
+		dev_info->exit(dev_info);
+
 	if (dev_info->edac_dev) {
 		edac_device_del_device(dev_info->edac_dev->dev);
 		edac_device_free_ctl_info(dev_info->edac_dev);
 	}
 
-	if (dev_info->exit)
-		dev_info->exit(dev_info);
-
 	pci_dev_put(dev_info->dev);
 }
 
@@ -437,25 +625,24 @@ static int amd8111_pci_probe(struct pci_dev *dev,
 				const struct pci_device_id *id)
 {
 	struct amd8111_pci_info *pci_info = &amd8111_pcis[id->driver_data];
+	int ret = -ENODEV;
 
 	pci_info->dev = pci_get_device(PCI_VENDOR_ID_AMD,
 					pci_info->err_dev, NULL);
-
 	if (!pci_info->dev) {
 		printk(KERN_ERR "EDAC device not found:"
 			"vendor %x, device %x, name %s\n",
 			PCI_VENDOR_ID_AMD, pci_info->err_dev,
 			pci_info->ctl_name);
-		return -ENODEV;
+		goto out;
 	}
 
 	if (pci_enable_device(pci_info->dev)) {
-		pci_dev_put(pci_info->dev);
 		printk(KERN_ERR "failed to enable:"
 			"vendor %x, device %x, name %s\n",
 			PCI_VENDOR_ID_AMD, pci_info->err_dev,
 			pci_info->ctl_name);
-		return -ENODEV;
+		goto err1;
 	}
 
 	/*
@@ -465,8 +652,10 @@ static int amd8111_pci_probe(struct pci_dev *dev,
 	*/
 	pci_info->edac_idx = edac_pci_alloc_index();
 	pci_info->edac_dev = edac_pci_alloc_ctl_info(0, pci_info->ctl_name);
-	if (!pci_info->edac_dev)
-		return -ENOMEM;
+	if (!pci_info->edac_dev) {
+		ret = -ENOMEM;
+		goto err1;
+	}
 
 	pci_info->edac_dev->pvt_info = pci_info;
 	pci_info->edac_dev->dev = &pci_info->dev->dev;
@@ -474,7 +663,7 @@ static int amd8111_pci_probe(struct pci_dev *dev,
 	pci_info->edac_dev->ctl_name = pci_info->ctl_name;
 	pci_info->edac_dev->dev_name = dev_name(&pci_info->dev->dev);
 
-	if (edac_op_state == EDAC_OPSTATE_POLL)
+	if (amd8111_op_state == EDAC_OPSTATE_POLL)
 		pci_info->edac_dev->edac_check = pci_info->check;
 
 	if (pci_info->init)
@@ -483,16 +672,27 @@ static int amd8111_pci_probe(struct pci_dev *dev,
 	if (edac_pci_add_device(pci_info->edac_dev, pci_info->edac_idx) > 0) {
 		printk(KERN_ERR "failed to add edac_pci for %s\n",
 			pci_info->ctl_name);
-		edac_pci_free_ctl_info(pci_info->edac_dev);
-		return -ENODEV;
+		ret = -ENOMEM;
+		goto err2;
 	}
 
-	printk(KERN_INFO "added one edac_pci on AMD8111 "
+	printk(KERN_INFO "added one device on AMD8111 "
 		"vendor %x, device %x, name %s\n",
 		PCI_VENDOR_ID_AMD, pci_info->err_dev,
 		pci_info->ctl_name);
 
-	return 0;
+	ret = 0;
+	goto out;
+
+err2:
+	if (pci_info->exit)
+		pci_info->exit(pci_info);
+
+	edac_pci_free_ctl_info(pci_info->edac_dev);
+err1:
+	pci_dev_put(pci_info->dev);
+out:
+	return ret;
 }
 
 static void amd8111_pci_remove(struct pci_dev *dev)
@@ -506,14 +706,14 @@ static void amd8111_pci_remove(struct pci_dev *dev)
 	if (!pci_info->err_dev)	/* should never happen */
 		return;
 
+	if (pci_info->exit)
+		pci_info->exit(pci_info);
+
 	if (pci_info->edac_dev) {
 		edac_pci_del_device(pci_info->edac_dev->dev);
 		edac_pci_free_ctl_info(pci_info->edac_dev);
 	}
 
-	if (pci_info->exit)
-		pci_info->exit(pci_info);
-
 	pci_dev_put(pci_info->dev);
 }
 
@@ -527,9 +727,7 @@ static const struct pci_device_id amd8111_edac_dev_tbl[] = {
 	.class_mask = 0,
 	.driver_data = LPC_BRIDGE,
 	},
-	{
-	0,
-	}			/* table is NULL-terminated */
+	{0}	/* table is NULL-terminated */
 };
 MODULE_DEVICE_TABLE(pci, amd8111_edac_dev_tbl);
 
@@ -550,9 +748,7 @@ static const struct pci_device_id amd8111_edac_pci_tbl[] = {
 	.class_mask = 0,
 	.driver_data = PCI_BRIDGE,
 	},
-	{
-	0,
-	}			/* table is NULL-terminated */
+	{0}	/* table is NULL-terminated */
 };
 MODULE_DEVICE_TABLE(pci, amd8111_edac_pci_tbl);
 
@@ -563,6 +759,69 @@ static struct pci_driver amd8111_edac_pci_driver = {
 	.id_table = amd8111_edac_pci_tbl,
 };
 
+/*
+ * AMD8111 NMI handler - check Legacy ISA Bridge and PCI Bridge
+ * to claim any possible NMI instance.
+ * Southbridge NMI Request messages posted through Hypertransport
+ * Channel will be transferred to a MPIC interrupt instance.
+ */
+static irqreturn_t amd8111_nmi_handler(int irq, void *dev_id)
+{
+	struct amd8111_dev_info *dev_info;
+	struct amd8111_pci_info *pci_info;
+	irqreturn_t ret = IRQ_NONE;
+
+	for (dev_info = amd8111_devices; dev_info->err_dev; dev_info++)
+		if (dev_info->isr)
+			ret |= dev_info->isr(irq, dev_info->edac_dev);
+
+	for (pci_info = amd8111_pcis; pci_info->err_dev; pci_info++)
+		if (pci_info->isr)
+			ret |= pci_info->isr(irq, pci_info->edac_dev);
+
+	return ret;
+}
+
+static void __init amd8111_nmi_handler_setup(void)
+{
+	int ret;
+
+	if (amd8111_op_state != EDAC_OPSTATE_NMI)
+		return;
+
+	amd8111_nmi_irq = NO_IRQ;
+
+#ifdef CONFIG_MPIC
+	amd8111_nmi_irq = edac_get_mpic_irq(MPIC_HWIRQ_HT_NMI);
+#endif
+
+	if (amd8111_nmi_irq == NO_IRQ) {
+		printk(KERN_ERR "%s: failed to get virq "
+			"for AMD8111 NMI requests\n", __func__);
+		return;
+	}
+
+	ret = request_irq(amd8111_nmi_irq, amd8111_nmi_handler,
+			  IRQF_SHARED, "[EDAC] AMD8111", amd8111_devices);
+	if (ret < 0) {
+		printk(KERN_INFO "%s: failed to request irq %d for "
+			"AMD8111 NMI requests\n", __func__, amd8111_nmi_irq);
+		return;
+	}
+
+	debugf0("%s: Successfully requested irq %d for AMD8111 NMI requests\n",
+		__func__, amd8131_nmi_irq);
+}
+
+static void __exit amd8111_nmi_handler_exit(void)
+{
+	if (amd8111_op_state != EDAC_OPSTATE_NMI)
+		return;
+
+	if (amd8111_nmi_irq != NO_IRQ)
+		free_irq(amd8111_nmi_irq, amd8111_devices);
+}
+
 static int __init amd8111_edac_init(void)
 {
 	int val;
@@ -570,12 +829,12 @@ static int __init amd8111_edac_init(void)
 	printk(KERN_INFO "AMD8111 EDAC driver "	AMD8111_EDAC_REVISION "\n");
 	printk(KERN_INFO "\t(c) 2008 Wind River Systems, Inc.\n");
 
-	/* Only POLL mode supported so far */
-	edac_op_state = EDAC_OPSTATE_POLL;
-
 	val = pci_register_driver(&amd8111_edac_dev_driver);
 	val |= pci_register_driver(&amd8111_edac_pci_driver);
 
+	if (val == 0)
+		amd8111_nmi_handler_setup();
+
 	return val;
 }
 
@@ -583,8 +842,9 @@ static void __exit amd8111_edac_exit(void)
 {
 	pci_unregister_driver(&amd8111_edac_pci_driver);
 	pci_unregister_driver(&amd8111_edac_dev_driver);
-}
 
+	amd8111_nmi_handler_exit();
+}
 
 module_init(amd8111_edac_init);
 module_exit(amd8111_edac_exit);
diff --git a/drivers/edac/amd8111_edac.h b/drivers/edac/amd8111_edac.h
index 3579433..51776b1 100644
--- a/drivers/edac/amd8111_edac.h
+++ b/drivers/edac/amd8111_edac.h
@@ -33,9 +33,8 @@ enum pci_stscmd_bits {
 	PCI_STSCMD_RMA		= BIT(29),
 	PCI_STSCMD_RTA		= BIT(28),
 	PCI_STSCMD_SERREN	= BIT(8),
-	PCI_STSCMD_CLEAR_MASK	= (PCI_STSCMD_SSE |
-				   PCI_STSCMD_RMA |
-				   PCI_STSCMD_RTA)
+	PCI_STSCMD_NMI_MASK	= (PCI_STSCMD_RMA | PCI_STSCMD_RTA),
+	PCI_STSCMD_CLEAR_MASK	= (PCI_STSCMD_SSE | PCI_STSCMD_NMI_MASK),
 };
 
 /************************************************************
@@ -62,9 +61,10 @@ enum mem_limit_bits {
  ************************************************************/
 #define REG_HT_LINK	0xc4
 enum ht_link_bits {
+	HT_LINK_CRCERR	= BIT(8),
 	HT_LINK_LKFAIL	= BIT(4),
 	HT_LINK_CRCFEN	= BIT(1),
-	HT_LINK_CLEAR_MASK = (HT_LINK_LKFAIL)
+	HT_LINK_CLEAR_MASK = (HT_LINK_LKFAIL | HT_LINK_CRCERR)
 };
 
 /************************************************************
@@ -105,6 +105,39 @@ enum at_compat_bits {
 	AT_COMPAT_CLRSERR	= BIT(2),
 };
 
+#define REG_IO_CTRL_2 0x41
+enum io_ctrl_2_bits {
+	IO_CTRL_2_NMIDIS	= BIT(1),
+};
+
+/************************************************************
+ *	System Management Configuration Registers, DevB:3xXX
+ ************************************************************/
+#define REG_GEN_CONFIG_2 0x41
+enum gen_config_2_bits {
+	GEN_CONFIG_2_PMIOEN	= BIT(7),
+};
+
+#define REG_SYSMAN_IO_SPACE 0x58
+#define SYSMAN_IO_SPACE_PMBASE_MASK 0xff00
+
+/************************************************************
+ *	System Management I/O Space, PMxx
+ ************************************************************/
+#define AMD8111_SYSMAN_IO_SIZE 256
+#define IO_TCO_CTRL_1 0x48
+enum io_tco_ctrl_1_bits {
+	IO_TCO_CTRL_1_NMI2SMI_EN = BIT(9),
+};
+
+/************************************************************
+ *	Real-Time Clock Port I/O
+ ************************************************************/
+#define REG_RTC	0x70
+enum rtc_bits {
+	RTC_NMIDIS = BIT(7),
+};
+
 struct amd8111_dev_info {
 	u16 err_dev;	/* PCI Device ID */
 	struct pci_dev *dev;
@@ -114,6 +147,7 @@ struct amd8111_dev_info {
 	void (*init)(struct amd8111_dev_info *dev_info);
 	void (*exit)(struct amd8111_dev_info *dev_info);
 	void (*check)(struct edac_device_ctl_info *edac_dev);
+	irqreturn_t (*isr)(int irq, void *dev_id);
 };
 
 struct amd8111_pci_info {
@@ -125,6 +159,7 @@ struct amd8111_pci_info {
 	void (*init)(struct amd8111_pci_info *dev_info);
 	void (*exit)(struct amd8111_pci_info *dev_info);
 	void (*check)(struct edac_pci_ctl_info *edac_dev);
+	irqreturn_t (*isr)(int irq, void *dev_id);
 };
 
 #endif /* _AMD8111_EDAC_H_ */
-- 
1.5.6.2

^ permalink raw reply related

* [v1 PATCH 2/4] EDAC: MCE & INT mode support for CPC925 driver
From: Harry Ciao @ 2009-05-18 10:04 UTC (permalink / raw)
  To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel
In-Reply-To: <1242641100-15324-2-git-send-email-qingtao.cao@windriver.com>

Support EDAC INT mode and add a new EDAC MCE mode for CPC925 EDAC driver.
CPC925 Hypertransport hostbridge controller may trigger interrupt that
latches MPIC INT2 pin on Hypertransport Link Errors, and generate MCE on
memory ECC Errors and Processor Interface Errors.

The global variable "edac_op_state" defined by EDAC core will be
obsolete, not only different EDAC modules on the same machine may
operate in different EDAC modes, but further this could be the
case for different EDAC devices of the same EDAC module, for example,
each CPC925 EDAC device could work in the mode specified by their own
"op_state" member in their private structure.

A spinlock will be used to protect the EDAC MCE handler from being
silently unregistered, however, it also implies a constraint that
when EDAC MCE handler is called on one CPU, it will be bypassed by
another MCE event on other CPUs.

Following aspects for this patch have been tested:
1, module initialization and deletion;
2, creation and deletion for the mapping between hwirq==2 to a virq
   for the Hypertransport Link Errors;
3, registration and unregistration for the EDAC MCE handler from the
   generic MCE handler on PPC;

Note, due to the difficulty and complexity to generate a real hardware
ECC/HT Link/CPU Errors, below aspects have not been tested yet:
1, if ECC or CPU Errors would generate MCE event;
2, if HT Link Error will indeed latch MPIC INT2 pin;
3, if EDAC isr/mce methods could handle errors correctly.

Signed-off-by: Harry Ciao <qingtao.cao@windriver.com>
---
 arch/powerpc/kernel/traps.c |   16 +++
 drivers/edac/cpc925_edac.c  |  275 ++++++++++++++++++++++++++++++++++++++++---
 drivers/edac/edac_stub.c    |    6 +
 include/linux/edac.h        |    6 +
 4 files changed, 284 insertions(+), 19 deletions(-)

diff --git a/arch/powerpc/kernel/traps.c b/arch/powerpc/kernel/traps.c
index 678fbff..1ae3465 100644
--- a/arch/powerpc/kernel/traps.c
+++ b/arch/powerpc/kernel/traps.c
@@ -57,6 +57,10 @@
 #include <asm/dbell.h>
 #endif
 
+#ifdef CONFIG_EDAC
+#include <linux/edac.h>
+#endif
+
 #if defined(CONFIG_DEBUGGER) || defined(CONFIG_KEXEC)
 int (*__debugger)(struct pt_regs *regs);
 int (*__debugger_ipi)(struct pt_regs *regs);
@@ -481,6 +485,18 @@ int machine_check_generic(struct pt_regs *regs)
 	default:
 		printk("Unknown values in msr\n");
 	}
+
+#ifdef CONFIG_EDAC
+	if (spin_trylock(&edac_mce_lock)) {
+		if (edac_mce_handler) {
+			int ret = edac_mce_handler();
+			spin_unlock(&edac_mce_lock);
+			return ret;
+		}
+		spin_unlock(&edac_mce_lock);
+	}
+#endif
+
 	return 0;
 }
 #endif /* everything else */
diff --git a/drivers/edac/cpc925_edac.c b/drivers/edac/cpc925_edac.c
index 8c54196..36fc506 100644
--- a/drivers/edac/cpc925_edac.c
+++ b/drivers/edac/cpc925_edac.c
@@ -25,6 +25,8 @@
 #include <linux/edac.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <asm/reg.h>
 
 #include "edac_core.h"
 #include "edac_module.h"
@@ -273,22 +275,29 @@ enum brgctrl_bits {
 
 /* Private structure for edac memory controller */
 struct cpc925_mc_pdata {
+	int op_state;
 	void __iomem *vbase;
 	unsigned long total_mem;
 	const char *name;
 	int edac_idx;
+	struct mem_ctl_info *mci;
+	int (*mce)(struct mem_ctl_info *mci);
 };
 
 /* Private structure for common edac device */
 struct cpc925_dev_info {
+	int op_state;
 	void __iomem *vbase;
 	struct platform_device *pdev;
 	char *ctl_name;
 	int edac_idx;
 	struct edac_device_ctl_info *edac_dev;
+	int irq;
 	void (*init)(struct cpc925_dev_info *dev_info);
 	void (*exit)(struct cpc925_dev_info *dev_info);
 	void (*check)(struct edac_device_ctl_info *edac_dev);
+	int (*mce)(struct edac_device_ctl_info *edac_dev);
+	irqreturn_t (*isr)(int irq, void *dev_id);
 };
 
 /* Get total memory size from Open Firmware DTB */
@@ -382,6 +391,18 @@ static void cpc925_init_csrows(struct mem_ctl_info *mci)
 	}
 }
 
+/* Set up HID0_EMCP bit if necessary, MSR[ME] has been set up */
+static void cpc925_mce_enable(void)
+{
+	unsigned long hid0 = mfspr(SPRN_HID0);
+
+	if ((hid0 & HID0_EMCP) == 0)
+		mtspr(SPRN_HID0, hid0 | HID0_EMCP);
+
+	debugf0("%s: MSR[ME] = %d, HID0[EMCP] = %d\n", __func__,
+		mfmsr() & MSR_ME, mfspr(SPRN_HID0));
+}
+
 /* Enable memory controller ECC detection */
 static void cpc925_mc_init(struct mem_ctl_info *mci)
 {
@@ -402,6 +423,9 @@ static void cpc925_mc_init(struct mem_ctl_info *mci)
 		mccr |= MCCR_ECC_EN;
 		__raw_writel(mccr, pdata->vbase + REG_MCCR_OFFSET);
 	}
+
+	if (pdata->op_state == EDAC_OPSTATE_MCE)
+		cpc925_mce_enable();
 }
 
 /* Disable memory controller ECC detection */
@@ -520,7 +544,10 @@ static int cpc925_mc_find_channel(struct mem_ctl_info *mci, u16 syndrome)
 	return 1;
 }
 
-/* Check memory controller registers for ECC errors */
+/*
+ * Check memory controller registers for ECC errors,
+ * called when EDAC MC works in POLL mode.
+ */
 static void cpc925_mc_check(struct mem_ctl_info *mci)
 {
 	struct cpc925_mc_pdata *pdata = mci->pvt_info;
@@ -579,6 +606,70 @@ static void cpc925_mc_check(struct mem_ctl_info *mci)
 		syndrome);
 }
 
+/*
+ * Check memory controller registers for ECC errors,
+ * called when EDAC MC works in MCE mode.
+ */
+static int cpc925_mc_mce(struct mem_ctl_info *mci)
+{
+	struct cpc925_mc_pdata *pdata = mci->pvt_info;
+	u32 apiexcp;
+	u32 mear;
+	u32 mesr;
+	u16 syndrome;
+	unsigned long pfn = 0, offset = 0;
+	int csrow = 0, channel = 0;
+
+	/* APIEXCP is cleared when read */
+	apiexcp = __raw_readl(pdata->vbase + REG_APIEXCP_OFFSET);
+	if ((apiexcp & ECC_EXCP_DETECTED) == 0)
+		return 0;
+
+	mesr = __raw_readl(pdata->vbase + REG_MESR_OFFSET);
+	syndrome = mesr | (MESR_ECC_SYN_H_MASK | MESR_ECC_SYN_L_MASK);
+
+	mear = __raw_readl(pdata->vbase + REG_MEAR_OFFSET);
+
+	/* Revert column/row addresses into page frame number, etc */
+	cpc925_mc_get_pfn(mci, mear, &pfn, &offset, &csrow);
+
+	if (apiexcp & CECC_EXCP_DETECTED) {
+		cpc925_mc_printk(mci, KERN_EMERG, "DRAM CECC Fault\n");
+		channel = cpc925_mc_find_channel(mci, syndrome);
+		edac_mc_handle_ce(mci, pfn, offset, syndrome,
+				  csrow, channel, mci->ctl_name);
+	}
+
+	if (apiexcp & UECC_EXCP_DETECTED) {
+		cpc925_mc_printk(mci, KERN_EMERG, "DRAM UECC Fault\n");
+		edac_mc_handle_ue(mci, pfn, offset, csrow, mci->ctl_name);
+	}
+
+	cpc925_mc_printk(mci, KERN_EMERG, "Dump registers:\n");
+	cpc925_mc_printk(mci, KERN_EMERG, "APIMASK               0x%08x\n",
+		__raw_readl(pdata->vbase + REG_APIMASK_OFFSET));
+	cpc925_mc_printk(mci, KERN_EMERG, "APIEXCP               0x%08x\n",
+		apiexcp);
+	cpc925_mc_printk(mci, KERN_EMERG, "Mem Scrub Ctrl        0x%08x\n",
+		__raw_readl(pdata->vbase + REG_MSCR_OFFSET));
+	cpc925_mc_printk(mci, KERN_EMERG, "Mem Scrub Rge Start   0x%08x\n",
+		__raw_readl(pdata->vbase + REG_MSRSR_OFFSET));
+	cpc925_mc_printk(mci, KERN_EMERG, "Mem Scrub Rge End     0x%08x\n",
+		__raw_readl(pdata->vbase + REG_MSRER_OFFSET));
+	cpc925_mc_printk(mci, KERN_EMERG, "Mem Scrub Pattern     0x%08x\n",
+		__raw_readl(pdata->vbase + REG_MSPR_OFFSET));
+	cpc925_mc_printk(mci, KERN_EMERG, "Mem Chk Ctrl          0x%08x\n",
+		__raw_readl(pdata->vbase + REG_MCCR_OFFSET));
+	cpc925_mc_printk(mci, KERN_EMERG, "Mem Chk Rge End       0x%08x\n",
+		__raw_readl(pdata->vbase + REG_MCRER_OFFSET));
+	cpc925_mc_printk(mci, KERN_EMERG, "Mem Err Address       0x%08x\n",
+		mesr);
+	cpc925_mc_printk(mci, KERN_EMERG, "Mem Err Syndrome      0x%08x\n",
+		syndrome);
+
+	return 1;
+}
+
 /******************** CPU err device********************************/
 /* Enable CPU Errors detection */
 static void cpc925_cpu_init(struct cpc925_dev_info *dev_info)
@@ -609,7 +700,7 @@ static void cpc925_cpu_exit(struct cpc925_dev_info *dev_info)
 	return;
 }
 
-/* Check for CPU Errors */
+/* Check for CPU Errors, called in POLL mode */
 static void cpc925_cpu_check(struct edac_device_ctl_info *edac_dev)
 {
 	struct cpc925_dev_info *dev_info = edac_dev->pvt_info;
@@ -630,6 +721,28 @@ static void cpc925_cpu_check(struct edac_device_ctl_info *edac_dev)
 	edac_device_handle_ue(edac_dev, 0, 0, edac_dev->ctl_name);
 }
 
+/* Check for CPU Errors, called in MCE mode */
+static int cpc925_cpu_mce(struct edac_device_ctl_info *edac_dev)
+{
+	struct cpc925_dev_info *dev_info = edac_dev->pvt_info;
+	u32 apiexcp;
+	u32 apimask;
+
+	/* APIEXCP is cleared when read */
+	apiexcp = __raw_readl(dev_info->vbase + REG_APIEXCP_OFFSET);
+	if ((apiexcp & CPU_EXCP_DETECTED) == 0)
+		return 0;
+
+	apimask = __raw_readl(dev_info->vbase + REG_APIMASK_OFFSET);
+	cpc925_printk(KERN_EMERG, "Processor Interface Fault\n"
+				  "Processor Interface register dump:\n");
+	cpc925_printk(KERN_EMERG, "APIMASK               0x%08x\n", apimask);
+	cpc925_printk(KERN_EMERG, "APIEXCP               0x%08x\n", apiexcp);
+
+	edac_device_handle_ue(edac_dev, 0, 0, edac_dev->ctl_name);
+	return 1;
+}
+
 /******************** HT Link err device****************************/
 /* Enable HyperTransport Link Error detection */
 static void cpc925_htlink_init(struct cpc925_dev_info *dev_info)
@@ -704,23 +817,105 @@ static void cpc925_htlink_check(struct edac_device_ctl_info *edac_dev)
 	edac_device_handle_ce(edac_dev, 0, 0, edac_dev->ctl_name);
 }
 
+static irqreturn_t cpc925_htlink_isr(int irq, void *dev_id)
+{
+	struct edac_device_ctl_info *edac_dev = dev_id;
+	struct cpc925_dev_info *dev_info = edac_dev->pvt_info;
+	u32 brgctrl = __raw_readl(dev_info->vbase + REG_BRGCTRL_OFFSET);
+	u32 linkctrl = __raw_readl(dev_info->vbase + REG_LINKCTRL_OFFSET);
+	u32 errctrl = __raw_readl(dev_info->vbase + REG_ERRCTRL_OFFSET);
+	u32 linkerr = __raw_readl(dev_info->vbase + REG_LINKERR_OFFSET);
+
+	if (!((brgctrl & BRGCTRL_DETSERR) ||
+	      (linkctrl & HT_LINKCTRL_DETECTED) ||
+	      (errctrl & HT_ERRCTRL_DETECTED) ||
+	      (linkerr & HT_LINKERR_DETECTED)))
+		return IRQ_NONE;
+
+	cpc925_htlink_check(edac_dev);
+
+	return IRQ_HANDLED;
+}
+
+/* Private structure for EDAC Memory Controller */
+static struct cpc925_mc_pdata cpc925_mc_private = {
+	/* EDAC MC supports POLL and MCE mode */
+	.op_state = EDAC_OPSTATE_MCE,
+	.mce = cpc925_mc_mce,
+	.mci = NULL,
+};
+
+/*
+ * Private strucutures for common EDAC devices for CPU Error
+ * and Hypertransport Link Error
+ */
 static struct cpc925_dev_info cpc925_devs[] = {
 	{
+	/* CPU Error supports POLL and MCE mode */
+	.op_state = EDAC_OPSTATE_MCE,
 	.ctl_name = CPC925_CPU_ERR_DEV,
 	.init = cpc925_cpu_init,
 	.exit = cpc925_cpu_exit,
 	.check = cpc925_cpu_check,
+	.mce = cpc925_cpu_mce,
 	},
 	{
+	/* Hypertransport Link Error supports POLL and INT mode */
+	.op_state = EDAC_OPSTATE_INT,
 	.ctl_name = CPC925_HT_LINK_DEV,
 	.init = cpc925_htlink_init,
 	.exit = cpc925_htlink_exit,
 	.check = cpc925_htlink_check,
+	.irq = NO_IRQ,
+	.isr = cpc925_htlink_isr,
 	},
 	{0}, /* Terminated by NULL */
 };
 
 /*
+ * MCE handler for EDAC CPC925 driver, check memory controller and
+ * Hypertransport hostbridge to claim any possbile MCE instance.
+ */
+static int cpc925_mce_handler(void)
+{
+	struct cpc925_mc_pdata *pdata = &cpc925_mc_private;
+	struct cpc925_dev_info *dev_info;
+	int ret = 0;
+
+	if (pdata->op_state == EDAC_OPSTATE_MCE)
+		if (pdata->mce)
+			ret |= pdata->mce(pdata->mci);
+
+	for (dev_info = &cpc925_devs[0]; dev_info->init; dev_info++) {
+		if (dev_info->op_state == EDAC_OPSTATE_MCE)
+			if (dev_info->mce)
+				ret |= dev_info->mce(dev_info->edac_dev);
+	}
+
+	return ret;
+}
+
+/* Hook CPC925 MCE handler to PowerPC generic MCE handler */
+static void cpc925_mce_handler_setup(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&edac_mce_lock, flags);
+	edac_mce_handler = cpc925_mce_handler;
+	spin_unlock_irqrestore(&edac_mce_lock, flags);
+}
+
+static void cpc925_mce_handler_exit(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&edac_mce_lock, flags);
+	if (edac_mce_handler)
+		edac_mce_handler = NULL;
+	spin_unlock_irqrestore(&edac_mce_lock, flags);
+}
+
+/*
  * Add CPU Err detection and HyperTransport Link Err detection
  * as common "edac_device", they have no corresponding device
  * nodes in the Open Firmware DTB and we have to add platform
@@ -730,6 +925,7 @@ static struct cpc925_dev_info cpc925_devs[] = {
 static void cpc925_add_edac_devices(void __iomem *vbase)
 {
 	struct cpc925_dev_info *dev_info;
+	int ret = 0;
 
 	if (!vbase) {
 		cpc925_printk(KERN_ERR, "MMIO not established yet\n");
@@ -766,8 +962,36 @@ static void cpc925_add_edac_devices(void __iomem *vbase)
 		dev_info->edac_dev->mod_name = CPC925_EDAC_MOD_STR;
 		dev_info->edac_dev->dev_name = dev_name(&dev_info->pdev->dev);
 
-		if (edac_op_state == EDAC_OPSTATE_POLL)
+		if (dev_info->op_state == EDAC_OPSTATE_POLL)
 			dev_info->edac_dev->edac_check = dev_info->check;
+		else if (dev_info->irq == EDAC_OPSTATE_MCE) {
+			/*
+			 * do nothing, MCE handler has been registered
+			 * by memory controller.
+			 */
+		} else if (dev_info->op_state == EDAC_OPSTATE_INT) {
+			dev_info->irq =
+				edac_get_mpic_irq(MPIC_HWIRQ_INTERNAL_ERROR);
+			if (dev_info->irq == NO_IRQ) {
+				cpc925_printk(KERN_ERR,	"%s: failed to get "
+					"virq for %s\n", __func__,
+					dev_info->ctl_name);
+				goto err2;
+			}
+
+			ret = request_irq(dev_info->irq, dev_info->isr,
+					IRQF_SHARED, "[EDAC] CPC925 ",
+					dev_info->edac_dev);
+			if (ret < 0) {
+				cpc925_printk(KERN_INFO, "%s: failed to "
+					"request irq %d for %s\n", __func__,
+					dev_info->irq, dev_info->ctl_name);
+				goto err2;
+			}
+
+			debugf0("%s: Successfully requested irq %d for %s\n",
+				 __func__, dev_info->irq, dev_info->ctl_name);
+		}
 
 		if (dev_info->init)
 			dev_info->init(dev_info);
@@ -776,7 +1000,7 @@ static void cpc925_add_edac_devices(void __iomem *vbase)
 			cpc925_printk(KERN_ERR,
 				"Unable to add edac device for %s\n",
 				dev_info->ctl_name);
-			goto err2;
+			goto err3;
 		}
 
 		debugf0("%s: Successfully added edac device for %s\n",
@@ -784,9 +1008,13 @@ static void cpc925_add_edac_devices(void __iomem *vbase)
 
 		continue;
 
-err2:
+err3:
 		if (dev_info->exit)
 			dev_info->exit(dev_info);
+
+		if (dev_info->op_state == EDAC_OPSTATE_INT)
+			free_irq(dev_info->irq, dev_info->edac_dev);
+err2:
 		edac_device_free_ctl_info(dev_info->edac_dev);
 err1:
 		platform_device_unregister(dev_info->pdev);
@@ -802,15 +1030,17 @@ static void cpc925_del_edac_devices(void)
 	struct cpc925_dev_info *dev_info;
 
 	for (dev_info = &cpc925_devs[0]; dev_info->init; dev_info++) {
+		if (dev_info->exit)
+			dev_info->exit(dev_info);
+
 		if (dev_info->edac_dev) {
+			if (dev_info->op_state == EDAC_OPSTATE_INT)
+				free_irq(dev_info->irq, dev_info->edac_dev);
 			edac_device_del_device(dev_info->edac_dev->dev);
 			edac_device_free_ctl_info(dev_info->edac_dev);
 			platform_device_unregister(dev_info->pdev);
 		}
 
-		if (dev_info->exit)
-			dev_info->exit(dev_info);
-
 		debugf0("%s: Successfully deleted edac device for %s\n",
 			__func__, dev_info->ctl_name);
 	}
@@ -900,18 +1130,18 @@ static int __devinit cpc925_probe(struct platform_device *pdev)
 	}
 
 	nr_channels = cpc925_mc_get_channels(vbase);
-	mci = edac_mc_alloc(sizeof(struct cpc925_mc_pdata),
-			CPC925_NR_CSROWS, nr_channels + 1, edac_mc_idx);
+	mci = edac_mc_alloc(0, CPC925_NR_CSROWS, nr_channels + 1, edac_mc_idx);
 	if (!mci) {
 		cpc925_printk(KERN_ERR, "No memory for mem_ctl_info\n");
 		res = -ENOMEM;
 		goto err2;
 	}
 
-	pdata = mci->pvt_info;
+	pdata = mci->pvt_info = &cpc925_mc_private;
 	pdata->vbase = vbase;
 	pdata->edac_idx = edac_mc_idx++;
 	pdata->name = pdev->name;
+	pdata->mci = mci;
 
 	mci->dev = &pdev->dev;
 	platform_set_drvdata(pdev, mci);
@@ -922,15 +1152,16 @@ static int __devinit cpc925_probe(struct platform_device *pdev)
 	mci->mod_name = CPC925_EDAC_MOD_STR;
 	mci->mod_ver = CPC925_EDAC_REVISION;
 	mci->ctl_name = pdev->name;
-
-	if (edac_op_state == EDAC_OPSTATE_POLL)
-		mci->edac_check = cpc925_mc_check;
-
 	mci->ctl_page_to_phys = NULL;
 	mci->scrub_mode = SCRUB_SW_SRC;
 	mci->set_sdram_scrub_rate = NULL;
 	mci->get_sdram_scrub_rate = cpc925_get_sdram_scrub_rate;
 
+	if (pdata->op_state == EDAC_OPSTATE_POLL)
+		mci->edac_check = cpc925_mc_check;
+	else if (pdata->op_state == EDAC_OPSTATE_MCE)
+		cpc925_mce_handler_setup();
+
 	cpc925_init_csrows(mci);
 
 	/* Setup memory controller registers */
@@ -951,6 +1182,10 @@ static int __devinit cpc925_probe(struct platform_device *pdev)
 
 err3:
 	cpc925_mc_exit(mci);
+
+	if (pdata->op_state == EDAC_OPSTATE_MCE)
+		cpc925_mce_handler_exit();
+
 	edac_mc_free(mci);
 err2:
 	devm_release_mem_region(&pdev->dev, r->start, r->end-r->start+1);
@@ -963,14 +1198,19 @@ out:
 static int cpc925_remove(struct platform_device *pdev)
 {
 	struct mem_ctl_info *mci = platform_get_drvdata(pdev);
+	struct cpc925_mc_pdata *pdata = mci->pvt_info;
 
 	/*
 	 * Delete common edac devices before edac mc, because
 	 * the former share the MMIO of the latter.
 	 */
 	cpc925_del_edac_devices();
+
 	cpc925_mc_exit(mci);
 
+	if (pdata->op_state == EDAC_OPSTATE_MCE)
+		cpc925_mce_handler_exit();
+
 	edac_mc_del_mc(&pdev->dev);
 	edac_mc_free(mci);
 
@@ -981,7 +1221,7 @@ static struct platform_driver cpc925_edac_driver = {
 	.probe = cpc925_probe,
 	.remove = cpc925_remove,
 	.driver = {
-		   .name = "cpc925_edac",
+		.name = "cpc925_edac",
 	}
 };
 
@@ -992,9 +1232,6 @@ static int __init cpc925_edac_init(void)
 	printk(KERN_INFO "IBM CPC925 EDAC driver " CPC925_EDAC_REVISION "\n");
 	printk(KERN_INFO "\t(c) 2008 Wind River Systems, Inc\n");
 
-	/* Only support POLL mode so far */
-	edac_op_state = EDAC_OPSTATE_POLL;
-
 	ret = platform_driver_register(&cpc925_edac_driver);
 	if (ret) {
 		printk(KERN_WARNING "Failed to register %s\n",
diff --git a/drivers/edac/edac_stub.c b/drivers/edac/edac_stub.c
index 20b428a..d2814d0 100644
--- a/drivers/edac/edac_stub.c
+++ b/drivers/edac/edac_stub.c
@@ -44,3 +44,9 @@ void edac_atomic_assert_error(void)
 	edac_err_assert++;
 }
 EXPORT_SYMBOL_GPL(edac_atomic_assert_error);
+
+int (*edac_mce_handler)(void) = NULL;
+EXPORT_SYMBOL_GPL(edac_mce_handler);
+
+DEFINE_SPINLOCK(edac_mce_lock);
+EXPORT_SYMBOL_GPL(edac_mce_lock);
diff --git a/include/linux/edac.h b/include/linux/edac.h
index c122d22..da2dc20 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -12,12 +12,14 @@
 #ifndef _LINUX_EDAC_H_
 #define _LINUX_EDAC_H_
 
+#include <linux/spinlock.h>
 #include <asm/atomic.h>
 
 #define EDAC_OPSTATE_INVAL	-1
 #define EDAC_OPSTATE_POLL	0
 #define EDAC_OPSTATE_NMI	1
 #define EDAC_OPSTATE_INT	2
+#define EDAC_OPSTATE_MCE	3
 
 extern int edac_op_state;
 extern int edac_err_assert;
@@ -26,11 +28,15 @@ extern atomic_t edac_handlers;
 extern int edac_handler_set(void);
 extern void edac_atomic_assert_error(void);
 
+extern int (*edac_mce_handler)(void);
+extern spinlock_t edac_mce_lock;
+
 static inline void opstate_init(void)
 {
 	switch (edac_op_state) {
 	case EDAC_OPSTATE_POLL:
 	case EDAC_OPSTATE_NMI:
+	case EDAC_OPSTATE_MCE:
 		break;
 	default:
 		edac_op_state = EDAC_OPSTATE_POLL;
-- 
1.5.6.2

^ permalink raw reply related

* the question about the DDR1_Rcomp code for INTEL IXP4XX CPU?
From: guojin02 @ 2009-05-18  9:50 UTC (permalink / raw)
  To: u-boot, linuxppc-dev

[-- Attachment #1: Type: text/plain, Size: 2880 bytes --]

Hi,every one,


I want to drive the  Rcomp circuit for ixp460, because I found that the ixp460 could not power up on low temperature(-40 degree).  I want to add those code on start.s, follow is the instruction from ixp460 developer's manual. Does anyone can help me realize those code from No.4 to No.6?





Instrction:
1. The MCU applies the clock DDRI_CK[2:0] at power up along with system power
(clock frequency unknown).
2. The MCU must stabilize DDRI_CK[2:0] within 100 μs after power stabilizes.
3. The MCU holds the following control inputs inactive:
DDRI_RAS_N, DDRI_CAS_N, DDRI_WE_N, DDRI_CS_N[1:0]
The MCU then places all of the following data outputs and strobes in the High-Z
state:
DDRI_DQS[4:0], DDRI_DQ[31:0], DDR_CB[7:0]

4. The MCU then performs sixteen RCOMP calibration cycles which take an additional
34 ms. Software MUST wait until this time has elapsed before continuing with DDRI
SDRAM initialization.
5. Software overwrites the default value of register DDR_RCOMP_CSR3 (Hex Offset
Address = 0x0CC00 F570) with a new value of 0x0000 1000.
6. Software overwrites the default value of register DDR_DRIVE3 (Hex Offset Address
= 0x0CC00 F5AC) with a new value of 0x0002 08F0.

7. Software disables the refresh counter by setting the RFR to zero.
8. Software issues one NOP cycle after the 200 us device deselect. A NOP is
accomplished by setting the SDIR to 00112. The MCU asserts DDRI_CKE[1:0] with
the NOP.
9. Software issues a precharge-all command to the DDRI SDRAM interface by setting
the SDIR to 00102.
10. Software issues an extended-mode-register-set command to enable the DLL by
writing 01002 to the SDIR. The MCU supports the following DDRI SDRAM mode
parameters:
11. After waiting Tmrd cycles, software issues a mode-register-set command by writing
00012 to the SDIR to program the DDRI SDRAM parameters and to reset the DLL.
The MCU supports the following DDRI SDRAM mode parameters:
12. After waiting Tmrd cycles, software issues a precharge-all command to the DDRI
SDRAM interface by setting the SDIR to 00102.
13. After waiting Trp cycles, software provides two auto-refresh cycles. An auto-refresh
cycle is accomplished by setting the SDIR to 01102. Software must ensure at least
Trfc cycles between each auto-refresh command.
14. Following the second auto-refresh cycle, software must wait Trfc cycles. Then,
software issues a mode-register-set command by writing to the SDIR to program the
DDRI SDRAM parameters without resetting the DLL by writing 00002 to the SDIR.
15. The MCU may issue a row-activate command Tmrd cycles after the mode-register-set
command.
16. Software re-enables the refresh counter by setting the RFR to the required value.
The waveform in Figure 109 illustrates the DDRI SDRAM initialization sequence.


2009-05-18 



    郭  劲
13810607876
010-62771694

[-- Attachment #2: Type: text/html, Size: 6424 bytes --]

^ permalink raw reply

* Re: Linuxppc-dev Digest, Vol 57, Issue 42
From: Hongjun Chen @ 2009-05-18  7:53 UTC (permalink / raw)
  To: wd; +Cc: linuxppc-dev@ozlabs.org
In-Reply-To: <mailman.7208.1241640931.26545.linuxppc-dev@ozlabs.org>

Why should you reinvent wheel for MPC512x DMA driver? We have a ready DMA driver, which has been used by MPC5121 AC97, VIU, ATA etc.

B.R,
Hongjun Chen
2009-05-18

发件人: linuxppc-dev-request@ozlabs.org
发送时间: 2009-05-07 04:38:43
收件人: linuxppc-dev@ozlabs.org
抄送: 
主题: Linuxppc-dev Digest, Vol 57, Issue 42

Send Linuxppc-dev mailing list submissions to
linuxppc-dev@ozlabs.org

To subscribe or unsubscribe via the World Wide Web, visit
https://ozlabs.org/mailman/listinfo/linuxppc-dev
or, via email, send a message with subject or body 'help' to
linuxppc-dev-request@ozlabs.org

You can reach the person managing the list at
linuxppc-dev-owner@ozlabs.org

When replying, please edit your Subject line so it is more specific
than "Re: Contents of Linuxppc-dev digest..."


Today's Topics:

   1. [PATCH 08/12] mpc5121: Added I2C support. (Wolfgang Denk)
   2. [PATCH 05/12] mpc5121ads: Added Reset Module node to DTS.
      (Wolfgang Denk)
   3. [PATCH 10/12] mpc5121: Add MPC5121 Real time clock driver.
      (Wolfgang Denk)
   4. [PATCH 11/12] mpc5121: Added MPC512x DMA driver. (Wolfgang Denk)


----------------------------------------------------------------------

Message: 1
Date: Wed,  6 May 2009 22:15:15 +0200
From: Wolfgang Denk  <wd@denx.de >
Subject: [PATCH 08/12] mpc5121: Added I2C support.
To: linuxppc-dev@ozlabs.org
Cc: linux-i2c@vger.kernel.org, Piotr Ziecik  <kosmo@semihalf.com >,
Wolfgang Denk  <wd@denx.de >
Message-ID:  <1241640919-4650-9-git-send-email-wd@denx.de >

From: Piotr Ziecik  <kosmo@semihalf.com >

- Enabled I2C interrupts on MPC5121.
- Updated Kconfig for i2c-mpc driver.

Signed-off-by: Piotr Ziecik  <kosmo@semihalf.com >
Signed-off-by: Wolfgang Denk  <wd@denx.de >
Cc:  <linux-i2c@vger.kernel.org >
Cc: Grant Likely  <grant.likely@secretlab.ca >
Cc: John Rigby  <jcrigby@gmail.com >
---
 arch/powerpc/platforms/512x/mpc5121_ads.c    |    2 ++
 arch/powerpc/platforms/512x/mpc512x.h        |    1 +
 arch/powerpc/platforms/512x/mpc512x_shared.c |   24 ++++++++++++++++++++++++
 drivers/i2c/busses/Kconfig                   |    9 +++++----
 4 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/arch/powerpc/platforms/512x/mpc5121_ads.c b/arch/powerpc/platforms/512x/mpc5121_ads.c
index 441abc4..a8976b4 100644
--- a/arch/powerpc/platforms/512x/mpc5121_ads.c
+++ b/arch/powerpc/platforms/512x/mpc5121_ads.c
@@ -42,6 +42,8 @@ static void __init mpc5121_ads_setup_arch(void)
  for_each_compatible_node(np, "pci", "fsl,mpc5121-pci")
  mpc83xx_add_bridge(np);
 #endif
+
+ mpc512x_init_i2c();
 }

 static void __init mpc5121_ads_init_IRQ(void)
diff --git a/arch/powerpc/platforms/512x/mpc512x.h b/arch/powerpc/platforms/512x/mpc512x.h
index 9c03693..f4db8a7 100644
--- a/arch/powerpc/platforms/512x/mpc512x.h
+++ b/arch/powerpc/platforms/512x/mpc512x.h
@@ -13,5 +13,6 @@
 #define __MPC512X_H__
 extern unsigned long mpc512x_find_ips_freq(struct device_node *node);
 extern void __init mpc512x_init_IRQ(void);
+extern void __init mpc512x_init_i2c(void);
 void __init mpc512x_declare_of_platform_devices(void);
 #endif /* __MPC512X_H__ */
diff --git a/arch/powerpc/platforms/512x/mpc512x_shared.c b/arch/powerpc/platforms/512x/mpc512x_shared.c
index 7135d89..b776e45 100644
--- a/arch/powerpc/platforms/512x/mpc512x_shared.c
+++ b/arch/powerpc/platforms/512x/mpc512x_shared.c
@@ -65,6 +65,30 @@ void __init mpc512x_init_IRQ(void)
  ipic_set_default_priority();
 }

+void __init mpc512x_init_i2c(void)
+{
+ struct device_node *np;
+ void __iomem *i2cctl;
+
+ /* Enable I2C interrupts */
+ np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-i2c-ctrl");
+ if (np) {
+ i2cctl = of_iomap(np, 0);
+ if (i2cctl) {
+ /*
+  * Set interrupt enable bits:
+  *  - I2C-0: bit 24,
+  *  - I2C-1: bit 26,
+  *  - I2C-2: bit 28.
+  */
+ out_be32(i2cctl, 0x15000000);
+ iounmap(i2cctl);
+ }
+
+ of_node_put(np);
+ }
+}
+
 /*
  * Nodes to do bus probe on, soc and localbus
  */
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index a48c8ae..57ed637 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -391,13 +391,14 @@ config I2C_IXP2000
    instead.

 config I2C_MPC
- tristate "MPC107/824x/85xx/52xx/86xx"
+ tristate "MPC107/824x/85xx/512x/52xx/86xx"
  depends on PPC32
  help
    If you say yes to this option, support will be included for the
-   built-in I2C interface on the MPC107/Tsi107/MPC8240/MPC8245 and
-   MPC85xx/MPC8641 family processors. The driver may also work on 52xx
-   family processors, though interrupts are known not to work.
+   built-in I2C interface on the MPC107/Tsi107/MPC8240/MPC8245,
+   MPC85xx/MPC8641 and MPC512x family processors. The driver may
+   also work on 52xx family processors, though interrupts are known
+   not to work.

    This driver can also be built as a module.  If so, the module
    will be called i2c-mpc.
-- 
1.6.0.6



------------------------------

Message: 2
Date: Wed,  6 May 2009 22:15:12 +0200
From: Wolfgang Denk  <wd@denx.de >
Subject: [PATCH 05/12] mpc5121ads: Added Reset Module node to DTS.
To: linuxppc-dev@ozlabs.org
Cc: Piotr Ziecik  <kosmo@semihalf.com >, Wolfgang Denk  <wd@denx.de >
Message-ID:  <1241640919-4650-6-git-send-email-wd@denx.de >

From: Piotr Ziecik  <kosmo@semihalf.com >

Signed-off-by: Piotr Ziecik  <kosmo@semihalf.com >
Signed-off-by: Wolfgang Denk  <wd@denx.de >
Cc: Grant Likely  <grant.likely@secretlab.ca >
Cc: John Rigby  <jcrigby@gmail.com >
---
 arch/powerpc/boot/dts/mpc5121ads.dts |    5 +++++
 1 files changed, 5 insertions(+), 0 deletions(-)

diff --git a/arch/powerpc/boot/dts/mpc5121ads.dts b/arch/powerpc/boot/dts/mpc5121ads.dts
index c2b8dbf..1b83a9d 100644
--- a/arch/powerpc/boot/dts/mpc5121ads.dts
+++ b/arch/powerpc/boot/dts/mpc5121ads.dts
@@ -166,6 +166,11 @@
  interrupt-parent =  < &ipic  >;
  };

+ reset@e00 { // Reset module
+ compatible = "fsl,mpc5121-reset";
+ reg =  <0xe00 0x100 >;
+ };
+
  clock@f00 { // Clock control
  compatible = "fsl,mpc5121-clock";
  reg =  <0xf00 0x100 >;
-- 
1.6.0.6



------------------------------

Message: 3
Date: Wed,  6 May 2009 22:15:17 +0200
From: Wolfgang Denk  <wd@denx.de >
Subject: [PATCH 10/12] mpc5121: Add MPC5121 Real time clock driver.
To: linuxppc-dev@ozlabs.org
Cc: Piotr Ziecik  <kosmo@semihalf.com >, rtc-linux@googlegroups.com,
John Rigby  <jrigby@freescale.com >, Wolfgang Denk  <wd@denx.de >
Message-ID:  <1241640919-4650-11-git-send-email-wd@denx.de >

From: John Rigby  <jrigby@freescale.com >

Based on Domen Puncer's rtc driver for 5200 posted to
the ppclinux mailing list:
http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
but never commited anywhere.

Changes to Domen's original:

    Changed filenames/routine names from mpc5200* to mpc5121*
    Changed match to only care about compatible and use "fsl,"
    convention for compatible.

    Make alarms more sane by dealing with lack of second alarm resolution.

    Deal with the fact that most of the 5121 rtc registers are not persistent
    across a reset even with a battery attached:

Use actual_time register for time keeping
and target_time register as an offset to linux time

The target_time register would normally be used for hibernation
but hibernation does not work on current silicon

Signed-off-by: John Rigby  <jrigby@freescale.com >
Signed-off-by: Piotr Ziecik  <kosmo@semihalf.com >
Signed-off-by: Wolfgang Denk  <wd@denx.de >
Cc:  <rtc-linux@googlegroups.com >
Cc: Grant Likely  <grant.likely@secretlab.ca >
Cc: John Rigby  <jcrigby@gmail.com >
---
 drivers/rtc/Kconfig       |   10 +
 drivers/rtc/Makefile      |    1 +
 drivers/rtc/rtc-mpc5121.c |  408 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 419 insertions(+), 0 deletions(-)
 create mode 100644 drivers/rtc/rtc-mpc5121.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 4e9851f..900d5b8 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -750,4 +750,14 @@ config RTC_DRV_PS3
    This driver can also be built as a module. If so, the module
    will be called rtc-ps3.

+config RTC_DRV_MPC5121
+ tristate "Freescale MPC5121 built-in RTC"
+ depends on RTC_CLASS
+ help
+   If you say yes here you will get support for the
+   built-in RTC MPC5121.
+
+   This driver can also be built as a module. If so, the module
+   will be called rtc-mpc5121.
+
 endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 6c0639a..8c6d6a7 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_RTC_DRV_STARFIRE) += rtc-starfire.o
 obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
 obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
 obj-$(CONFIG_RTC_DRV_MV) += rtc-mv.o
+obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o
 obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o
 obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
 obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
diff --git a/drivers/rtc/rtc-mpc5121.c b/drivers/rtc/rtc-mpc5121.c
new file mode 100644
index 0000000..63460cb
--- /dev/null
+++ b/drivers/rtc/rtc-mpc5121.c
@@ -0,0 +1,408 @@
+/*
+ * Real-time clock driver for MPC5121
+ *
+ * Copyright 2007, Domen Puncer  <domen.puncer@telargo.com >
+ * Copyright 2008, Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/*
+ * History:
+ *
+ * Based on mpc5200_rtc.c written by Domen Puncer  <domen.puncer@telargo.com >
+ *   posted to linuxppc-embedded mailing list:
+ *     http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
+ *   but never committed to any public tree.
+ *
+ * Author: John Rigby  <jrigby@freescale.com >
+ *   Converted to 5121 rtc driver.
+ *
+ *   Make alarms more sane by dealing with lack of second alarm resolution.
+ *
+ *   Use actual_time time register for time keeping since it is persistent
+ *   and the normal rtc registers are not.  Use target_time register as an
+ *   offset to linux time.
+ *
+ */
+
+#include  <linux/module.h >
+#include  <linux/rtc.h >
+#include  <linux/of_device.h >
+#include  <linux/of_platform.h >
+#include  <linux/io.h >
+
+struct mpc5121_rtc_regs {
+ u8 set_time; /* RTC + 0x00 */
+ u8 hour_set; /* RTC + 0x01 */
+ u8 minute_set; /* RTC + 0x02 */
+ u8 second_set; /* RTC + 0x03 */
+
+ u8 set_date; /* RTC + 0x04 */
+ u8 month_set; /* RTC + 0x05 */
+ u8 weekday_set; /* RTC + 0x06 */
+ u8 date_set; /* RTC + 0x07 */
+
+ u8 write_sw; /* RTC + 0x08 */
+ u8 sw_set; /* RTC + 0x09 */
+ u16 year_set; /* RTC + 0x0a */
+
+ u8 alm_enable; /* RTC + 0x0c */
+ u8 alm_hour_set; /* RTC + 0x0d */
+ u8 alm_min_set; /* RTC + 0x0e */
+ u8 int_enable; /* RTC + 0x0f */
+
+ u8 reserved1;
+ u8 hour; /* RTC + 0x11 */
+ u8 minute; /* RTC + 0x12 */
+ u8 second; /* RTC + 0x13 */
+
+ u8 month; /* RTC + 0x14 */
+ u8 wday_mday; /* RTC + 0x15 */
+ u16 year; /* RTC + 0x16 */
+
+ u8 int_alm; /* RTC + 0x18 */
+ u8 int_sw; /* RTC + 0x19 */
+ u8 alm_status; /* RTC + 0x1a */
+ u8 sw_minute; /* RTC + 0x1b */
+
+ u8 bus_error_1; /* RTC + 0x1c */
+ u8 int_day; /* RTC + 0x1d */
+ u8 int_min; /* RTC + 0x1e */
+ u8 int_sec; /* RTC + 0x1f */
+
+ /*
+  * target_time:
+  * intended to be used for hibernation but hibernation
+  * does not work on silicon rev 1.5 so use it for non-volatile
+  * storage of offset between the actual_time register and linux
+  * time
+  */
+ u32 target_time; /* RTC + 0x20 */
+ /*
+  * actual_time:
+  *  readonly time since VBAT_RTC was last connected
+  */
+ u32 actual_time; /* RTC + 0x24 */
+ u32 keep_alive; /* RTC + 0x28 */
+};
+
+struct mpc5121_rtc_data {
+ unsigned irq;
+ unsigned irq_periodic;
+ struct mpc5121_rtc_regs __iomem *regs;
+ struct rtc_device *rtc;
+ struct rtc_wkalrm wkalarm;
+};
+
+/*
+ * Update second/minute/hour registers.
+ *
+ * This is just so alarm will work.
+ */
+static void mpc5121_rtc_update_smh(struct mpc5121_rtc_regs __iomem *regs,
+ struct rtc_time *tm)
+{
+ out_8(®s- >second_set, tm- >tm_sec);
+ out_8(®s- >minute_set, tm- >tm_min);
+ out_8(®s- >hour_set, tm- >tm_hour);
+
+ /* set time sequence */
+ out_8(®s- >set_time, 0x1);
+ out_8(®s- >set_time, 0x3);
+ out_8(®s- >set_time, 0x1);
+ out_8(®s- >set_time, 0x0);
+}
+
+static int mpc5121_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+ unsigned long now;
+
+ /*
+  * linux time is actual_time plus the offset saved in target_time
+  */
+ now = in_be32(®s- >actual_time) + in_be32(®s- >target_time);
+
+ rtc_time_to_tm(now, tm);
+
+ /*
+  * update second minute hour registers
+  * so alarms will work
+  */
+ mpc5121_rtc_update_smh(regs, tm);
+
+ return 0;
+}
+
+static int mpc5121_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+ int ret;
+ unsigned long now;
+
+
+ /*
+  * The actual_time register is read only so we write the offset
+  * between it and linux time to the target_time register.
+  */
+ ret = rtc_tm_to_time(tm, &now);
+ if (ret == 0)
+ out_be32(®s- >target_time, now - in_be32(®s- >actual_time));
+
+ /*
+  * update second minute hour registers
+  * so alarms will work
+  */
+ mpc5121_rtc_update_smh(regs, tm);
+
+ return 0;
+}
+
+static int mpc5121_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ *alarm = rtc- >wkalarm;
+
+ alarm- >pending = in_8(®s- >alm_status);
+
+ return 0;
+}
+
+static int mpc5121_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ /*
+  * the alarm has no seconds so deal with it
+  */
+ if (alarm- >time.tm_sec) {
+ alarm- >time.tm_sec = 0;
+ alarm- >time.tm_min++;
+ if (alarm- >time.tm_min  >= 60) {
+ alarm- >time.tm_min = 0;
+ alarm- >time.tm_hour++;
+ if (alarm- >time.tm_hour  >= 24)
+ alarm- >time.tm_hour = 0;
+ }
+ }
+
+ alarm- >time.tm_mday = -1;
+ alarm- >time.tm_mon = -1;
+ alarm- >time.tm_year = -1;
+
+ out_8(®s- >alm_min_set, alarm- >time.tm_min);
+ out_8(®s- >alm_hour_set, alarm- >time.tm_hour);
+
+ out_8(®s- >alm_enable, alarm- >enabled);
+
+ rtc- >wkalarm = *alarm;
+ return 0;
+}
+
+static irqreturn_t mpc5121_rtc_handler(int irq, void *dev)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ if (in_8(®s- >int_alm)) {
+ /* acknowledge and clear status */
+ out_8(®s- >int_alm, 1);
+ out_8(®s- >alm_status, 1);
+
+ rtc_update_irq(rtc- >rtc, 1, RTC_IRQF | RTC_AF);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t mpc5121_rtc_handler_upd(int irq, void *dev)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ if (in_8(®s- >int_sec) && (in_8(®s- >int_enable) & 0x1)) {
+ /* acknowledge */
+ out_8(®s- >int_sec, 1);
+
+ rtc_update_irq(rtc- >rtc, 1, RTC_IRQF | RTC_UF);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int mpc5121_rtc_ioctl(struct device *dev, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ switch (cmd) {
+ /* alarm interrupt */
+ case RTC_AIE_ON:
+ out_8(®s- >alm_enable, 1);
+ rtc- >wkalarm.enabled = 1;
+ break;
+ case RTC_AIE_OFF:
+ out_8(®s- >alm_enable, 0);
+ rtc- >wkalarm.enabled = 0;
+ break;
+
+ /* update interrupt */
+ case RTC_UIE_ON:
+ out_8(®s- >int_enable,
+ (in_8(®s- >int_enable) & ~0x8) | 0x1);
+ break;
+ case RTC_UIE_OFF:
+ out_8(®s- >int_enable, in_8(®s- >int_enable) & ~0x1);
+ break;
+
+ /* no periodic interrupts */
+ case RTC_IRQP_READ:
+ case RTC_IRQP_SET:
+ return -ENOTTY;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static const struct rtc_class_ops mpc5121_rtc_ops = {
+ .read_time = mpc5121_rtc_read_time,
+ .set_time = mpc5121_rtc_set_time,
+ .read_alarm = mpc5121_rtc_read_alarm,
+ .set_alarm = mpc5121_rtc_set_alarm,
+ .ioctl = mpc5121_rtc_ioctl,
+};
+
+static int __devinit mpc5121_rtc_probe(struct of_device *op,
+ const struct of_device_id *match)
+{
+ struct mpc5121_rtc_data *rtc;
+ int err = 0;
+ u32 ka;
+
+ rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
+ if (!rtc) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ rtc- >regs = of_iomap(op- >node, 0);
+
+ if (!rtc- >regs) {
+ printk(KERN_ERR "%s: couldn't map io space\n", __func__);
+ err = -ENOSYS;
+ goto out_free;
+ }
+
+ device_init_wakeup(&op- >dev, 1);
+
+ rtc- >rtc = rtc_device_register("mpc5121-rtc", &op- >dev,
+ &mpc5121_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc- >rtc)) {
+ err = PTR_ERR(rtc- >rtc);
+ goto out_unmap;
+ }
+
+ dev_set_drvdata(&op- >dev, rtc);
+
+ rtc- >irq = irq_of_parse_and_map(op- >node, 1);
+ err = request_irq(rtc- >irq, mpc5121_rtc_handler, IRQF_DISABLED,
+ "mpc5121-rtc", &op- >dev);
+ if (err) {
+ printk(KERN_ERR "%s: could not request irq: %i\n",
+ __func__, rtc- >irq);
+ goto out_dispose;
+ }
+
+ rtc- >irq_periodic = irq_of_parse_and_map(op- >node, 0);
+ err = request_irq(rtc- >irq_periodic, mpc5121_rtc_handler_upd,
+  IRQF_DISABLED, "mpc5121-rtc_upd", &op- >dev);
+ if (err) {
+ printk(KERN_ERR "%s: could not request irq: %i\n",
+ __func__, rtc- >irq_periodic);
+ goto out_dispose2;
+ }
+
+ ka = in_be32(&rtc- >regs- >keep_alive);
+ if (ka & 0x02) {
+ printk(KERN_WARNING
+ "mpc5121-rtc: Battery or oscillator failure!\n");
+ out_be32(&rtc- >regs- >keep_alive, ka);
+ }
+
+ goto out;
+
+out_dispose2:
+ irq_dispose_mapping(rtc- >irq_periodic);
+ free_irq(rtc- >irq, &op- >dev);
+out_dispose:
+ irq_dispose_mapping(rtc- >irq);
+out_unmap:
+ iounmap(rtc- >regs);
+out_free:
+ kfree(rtc);
+out:
+ return err;
+}
+
+static int __devexit mpc5121_rtc_remove(struct of_device *op)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(&op- >dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ /* disable interrupt, so there are no nasty surprises */
+ out_8(®s- >alm_enable, 0);
+ out_8(®s- >int_enable, in_8(®s- >int_enable) & ~0x1);
+
+ rtc_device_unregister(rtc- >rtc);
+ iounmap(rtc- >regs);
+ free_irq(rtc- >irq, &op- >dev);
+ free_irq(rtc- >irq_periodic, &op- >dev);
+ irq_dispose_mapping(rtc- >irq);
+ irq_dispose_mapping(rtc- >irq_periodic);
+ dev_set_drvdata(&op- >dev, NULL);
+ kfree(rtc);
+
+ return 0;
+}
+
+static struct of_device_id mpc5121_rtc_match[] = {
+ { .compatible = "fsl,mpc5121-rtc", },
+ {},
+};
+
+static struct of_platform_driver mpc5121_rtc_driver = {
+ .owner = THIS_MODULE,
+ .name = "mpc5121-rtc",
+ .match_table = mpc5121_rtc_match,
+ .probe = mpc5121_rtc_probe,
+ .remove = mpc5121_rtc_remove,
+};
+
+static int __init mpc5121_rtc_init(void)
+{
+ return of_register_platform_driver(&mpc5121_rtc_driver);
+}
+
+static void __exit mpc5121_rtc_exit(void)
+{
+ of_unregister_platform_driver(&mpc5121_rtc_driver);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("John Rigby  <jrigby@freescale.com >");
+
+module_init(mpc5121_rtc_init);
+module_exit(mpc5121_rtc_exit);
-- 
1.6.0.6



------------------------------

Message: 4
Date: Wed,  6 May 2009 22:15:18 +0200
From: Wolfgang Denk  <wd@denx.de >
Subject: [PATCH 11/12] mpc5121: Added MPC512x DMA driver.
To: linuxppc-dev@ozlabs.org
Cc: Piotr Ziecik  <kosmo@semihalf.com >, Wolfgang Denk  <wd@denx.de >
Message-ID:  <1241640919-4650-12-git-send-email-wd@denx.de >

From: Piotr Ziecik  <kosmo@semihalf.com >

This patch adds initial version of MPC512x DMA driver.
Only memory to memory transfers are currenly supported.

Signed-off-by: Piotr Ziecik  <kosmo@semihalf.com >
Signed-off-by: Wolfgang Denk  <wd@denx.de >
Cc: Grant Likely  <grant.likely@secretlab.ca >
Cc: John Rigby  <jcrigby@gmail.com >
---
 arch/powerpc/boot/dts/mpc5121ads.dts         |    2 +-
 arch/powerpc/platforms/512x/mpc512x_shared.c |    1 +
 drivers/dma/Kconfig                          |    7 +
 drivers/dma/Makefile                         |    1 +
 drivers/dma/mpc512x_dma.c                    |  642 ++++++++++++++++++++++++++
 drivers/dma/mpc512x_dma.h                    |  192 ++++++++
 6 files changed, 844 insertions(+), 1 deletions(-)
 create mode 100644 drivers/dma/mpc512x_dma.c
 create mode 100644 drivers/dma/mpc512x_dma.h

diff --git a/arch/powerpc/boot/dts/mpc5121ads.dts b/arch/powerpc/boot/dts/mpc5121ads.dts
index c2d9de9..e7f0e09 100644
--- a/arch/powerpc/boot/dts/mpc5121ads.dts
+++ b/arch/powerpc/boot/dts/mpc5121ads.dts
@@ -373,7 +373,7 @@
  };

  dma@14000 {
- compatible = "fsl,mpc5121-dma2";
+ compatible = "fsl,mpc512x-dma";
  reg =  <0x14000 0x1800 >;
  interrupts =  <65 0x8 >;
  interrupt-parent =  < &ipic  >;
diff --git a/arch/powerpc/platforms/512x/mpc512x_shared.c b/arch/powerpc/platforms/512x/mpc512x_shared.c
index b776e45..135fd6b 100644
--- a/arch/powerpc/platforms/512x/mpc512x_shared.c
+++ b/arch/powerpc/platforms/512x/mpc512x_shared.c
@@ -95,6 +95,7 @@ void __init mpc512x_init_i2c(void)
 static struct of_device_id __initdata of_bus_ids[] = {
  { .compatible = "fsl,mpc5121-immr", },
  { .compatible = "fsl,mpc5121-localbus", },
+ { .compatible = "fsl,mpc5121-dma", },
  { .compatible = "fsl,mpc5121-nfc", },
  {},
 };
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 3b3c01b..e734a7a 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -55,6 +55,13 @@ config FSL_DMA
    The Elo is the DMA controller on some 82xx and 83xx parts, and the
    Elo Plus is the DMA controller on 85xx and 86xx parts.

+config MPC512X_DMA
+ tristate "Freescale MPC512x built-in DMA engine support"
+ depends on PPC_MPC512x
+ select DMA_ENGINE
+ ---help---
+   Enable support for the Freescale MPC512x built-in DMA engine.
+
 config MV_XOR
  bool "Marvell XOR engine support"
  depends on PLAT_ORION
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 2e5dc96..f02806c 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_INTEL_IOATDMA) += ioatdma.o
 ioatdma-objs := ioat.o ioat_dma.o ioat_dca.o
 obj-$(CONFIG_INTEL_IOP_ADMA) += iop-adma.o
 obj-$(CONFIG_FSL_DMA) += fsldma.o
+obj-$(CONFIG_MPC512X_DMA) += mpc512x_dma.o
 obj-$(CONFIG_MV_XOR) += mv_xor.o
 obj-$(CONFIG_DW_DMAC) += dw_dmac.o
 obj-$(CONFIG_MX3_IPU) += ipu/
diff --git a/drivers/dma/mpc512x_dma.c b/drivers/dma/mpc512x_dma.c
new file mode 100644
index 0000000..1aac897
--- /dev/null
+++ b/drivers/dma/mpc512x_dma.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) Semihalf 2009
+ *
+ * Written by: Piotr Ziecik  <kosmo@semihalf.com >
+ *
+ * Approved as OSADL project by a majority of OSADL members and funded
+ * by OSADL membership fees in 2009;  for details see www.osadl.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 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., 59
+ * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * The full GNU General Public License is included in this distribution in the
+ * file called COPYING.
+ */
+
+/*
+ * This is initial version of MPC5121 DMA driver. Only memory to memory
+ * transfers are supported (tested using dmatest module).
+ */
+
+#include  <linux/module.h >
+#include  <linux/dmaengine.h >
+#include  <linux/dma-mapping.h >
+#include  <linux/interrupt.h >
+#include  <linux/io.h >
+#include  <linux/of_device.h >
+#include  <linux/of_platform.h >
+
+#include  <linux/random.h >
+
+#include "mpc512x_dma.h"
+
+#define DRV_NAME "mpc512x_dma"
+
+/* Convert struct dma_chan to struct mpc_dma_chan */
+static inline struct mpc_dma_chan *dma_chan_to_mpc_dma_chan(struct dma_chan *c)
+{
+ return container_of(c, struct mpc_dma_chan, chan);
+}
+
+/* Convert struct dma_chan to struct mpc_dma */
+static inline struct mpc_dma *dma_chan_to_mpc_dma(struct dma_chan *c)
+{
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(c);
+ return container_of(mchan, struct mpc_dma, channels[c- >chan_id]);
+}
+
+/*
+ * Execute all queued DMA descriptors.
+ *
+ * Following requirements must be met while calling mpc_dma_execute():
+ *  a) mchan- >lock is acquired,
+ *  b) mchan- >active list is empty,
+ *  c) mchan- >queued list contains at least one entry.
+ */
+static void mpc_dma_execute(struct mpc_dma_chan *mchan)
+{
+ struct mpc_dma *mdma = dma_chan_to_mpc_dma(&mchan- >chan);
+ struct mpc_dma_desc *first = NULL;
+ struct mpc_dma_desc *prev = NULL;
+ struct mpc_dma_desc *mdesc;
+ int cid = mchan- >chan.chan_id;
+
+ /* Move all queued descriptors to active list */
+ list_splice_tail_init(&mchan- >queued, &mchan- >active);
+
+ /* Chain descriptors into one transaction */
+ list_for_each_entry(mdesc, &mchan- >active, node) {
+ if (!first)
+ first = mdesc;
+
+ if (!prev) {
+ prev = mdesc;
+ continue;
+ }
+
+ prev- >tcd- >dlast_sga = mdesc- >tcd_paddr;
+ prev- >tcd- >e_sg = 1;
+ mdesc- >tcd- >start = 1;
+
+ prev = mdesc;
+ }
+
+ prev- >tcd- >start = 0;
+ prev- >tcd- >int_maj = 1;
+
+ /* Send first descriptor in chain into hardware */
+ memcpy_toio(&mdma- >tcd[cid], first- >tcd, sizeof(struct mpc_dma_tcd));
+ out_8(&mdma- >regs- >dmassrt, cid);
+}
+
+/* Handle interrupt on one half of DMA controller (32 channels) */
+static void mpc_dma_irq_process(struct mpc_dma *mdma, u32 is, u32 es, int off)
+{
+ struct mpc_dma_chan *mchan;
+ struct mpc_dma_desc *mdesc;
+ u32 status = is | es;
+ int ch;
+
+ while ((ch = fls(status) - 1)  >= 0) {
+ status &= ~(1  < < ch);
+ mchan = &mdma- >channels[ch + off];
+
+ spin_lock(&mchan- >lock);
+
+ /* Check error status */
+ if (es & (1  < < ch))
+ list_for_each_entry(mdesc, &mchan- >active, node)
+ mdesc- >error = -EIO;
+
+ /* Execute queued descriptors */
+ list_splice_tail_init(&mchan- >active, &mchan- >completed);
+ if (!list_empty(&mchan- >queued))
+ mpc_dma_execute(mchan);
+
+ spin_unlock(&mchan- >lock);
+ }
+}
+
+/* Interrupt handler */
+static irqreturn_t mpc_dma_irq(int irq, void *data)
+{
+ struct mpc_dma *mdma = data;
+ uint es;
+
+ /* Save error status register */
+ es = in_be32(&mdma- >regs- >dmaes);
+ spin_lock(&mdma- >error_status_lock);
+ if ((es & MPC_DMA_DMAES_VLD) && mdma- >error_status == 0)
+ mdma- >error_status = es;
+ spin_unlock(&mdma- >error_status_lock);
+
+ /* Handle interrupt on each channel */
+ mpc_dma_irq_process(mdma, in_be32(&mdma- >regs- >dmainth),
+ in_be32(&mdma- >regs- >dmaerrh), 32);
+ mpc_dma_irq_process(mdma, in_be32(&mdma- >regs- >dmaintl),
+ in_be32(&mdma- >regs- >dmaerrl), 0);
+
+ /* Ack interrupt on all channels */
+ out_be32(&mdma- >regs- >dmainth, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaintl, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaerrh, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaerrl, 0xFFFFFFFF);
+
+ /* Schedule tasklet */
+ tasklet_schedule(&mdma- >tasklet);
+
+ return IRQ_HANDLED;
+}
+
+/* DMA Tasklet */
+static void mpc_dma_tasklet(unsigned long data)
+{
+ struct mpc_dma *mdma = (void *)data;
+ dma_cookie_t last_cookie = 0;
+ struct mpc_dma_chan *mchan;
+ struct mpc_dma_desc *mdesc;
+ struct dma_async_tx_descriptor *desc;
+ unsigned long flags;
+ LIST_HEAD(list);
+ uint es;
+ int i;
+
+ spin_lock_irqsave(&mdma- >error_status_lock, flags);
+ es = mdma- >error_status;
+ mdma- >error_status = 0;
+ spin_unlock_irqrestore(&mdma- >error_status_lock, flags);
+
+ /* Print nice error report */
+ if (es) {
+ dev_err(mdma- >dma.dev,
+ "Hardware reported following error(s) on channel %u:\n",
+       MPC_DMA_DMAES_ERRCHN(es));
+
+ if (es & MPC_DMA_DMAES_GPE)
+ dev_err(mdma- >dma.dev, "- Group Priority Error\n");
+ if (es & MPC_DMA_DMAES_CPE)
+ dev_err(mdma- >dma.dev, "- Channel Priority Error\n");
+ if (es & MPC_DMA_DMAES_SAE)
+ dev_err(mdma- >dma.dev, "- Source Address Error\n");
+ if (es & MPC_DMA_DMAES_SOE)
+ dev_err(mdma- >dma.dev, "- Source Offset"
+ " Configuration Error\n");
+ if (es & MPC_DMA_DMAES_DAE)
+ dev_err(mdma- >dma.dev, "- Destination Address"
+ " Error\n");
+ if (es & MPC_DMA_DMAES_DOE)
+ dev_err(mdma- >dma.dev, "- Destination Offset"
+ " Configuration Error\n");
+ if (es & MPC_DMA_DMAES_NCE)
+ dev_err(mdma- >dma.dev, "- NBytes/Citter"
+ " Configuration Error\n");
+ if (es & MPC_DMA_DMAES_SGE)
+ dev_err(mdma- >dma.dev, "- Scatter/Gather"
+ " Configuration Error\n");
+ if (es & MPC_DMA_DMAES_SBE)
+ dev_err(mdma- >dma.dev, "- Source Bus Error\n");
+ if (es & MPC_DMA_DMAES_DBE)
+ dev_err(mdma- >dma.dev, "- Destination Bus Error\n");
+ }
+
+ for (i = 0; i  < mdma- >dma.chancnt; i++) {
+ mchan = &mdma- >channels[i];
+
+ /* Get all completed descriptors */
+ spin_lock_irqsave(&mchan- >lock, flags);
+ if (!list_empty(&mchan- >completed))
+ list_splice_tail_init(&mchan- >completed, &list);
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ if (list_empty(&list))
+ continue;
+
+ /* Execute callbacks and run dependencies */
+ list_for_each_entry(mdesc, &list, node) {
+ desc = &mdesc- >desc;
+
+ if (desc- >callback)
+ desc- >callback(desc- >callback_param);
+
+ last_cookie = desc- >cookie;
+ dma_run_dependencies(desc);
+ }
+
+ /* Free descriptors */
+ spin_lock_irqsave(&mchan- >lock, flags);
+ list_splice_tail_init(&list, &mchan- >free);
+ mchan- >completed_cookie = last_cookie;
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+ }
+}
+
+/* Submit descriptor to hardware */
+static dma_cookie_t mpc_dma_tx_submit(struct dma_async_tx_descriptor *txd)
+{
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(txd- >chan);
+ struct mpc_dma_desc *mdesc;
+ unsigned long flags;
+ dma_cookie_t cookie;
+
+ mdesc = container_of(txd, struct mpc_dma_desc, desc);
+
+ spin_lock_irqsave(&mchan- >lock, flags);
+
+ /* Move descriptor to queue */
+ list_move_tail(&mdesc- >node, &mchan- >queued);
+
+ /* If channel is idle, execute all queued descriptors */
+ if (list_empty(&mchan- >active))
+ mpc_dma_execute(mchan);
+
+ /* Update cookie */
+ cookie = mchan- >chan.cookie + 1;
+ if (cookie  <= 0)
+ cookie = 1;
+
+ mchan- >chan.cookie = cookie;
+ mdesc- >desc.cookie = cookie;
+
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ return cookie;
+}
+
+/* Alloc channel resources */
+static int mpc_dma_alloc_chan_resources(struct dma_chan *chan)
+{
+ struct mpc_dma *mdma = dma_chan_to_mpc_dma(chan);
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan);
+ struct mpc_dma_desc *mdesc;
+ struct mpc_dma_tcd *tcd;
+ dma_addr_t tcd_paddr;
+ unsigned long flags;
+ LIST_HEAD(descs);
+ int i;
+
+ /* Alloc DMA memory for Transfer Control Descriptors */
+ tcd = dma_alloc_coherent(mdma- >dma.dev,
+ MPC_DMA_DESCRIPTORS * sizeof(struct mpc_dma_tcd),
+ &tcd_paddr, GFP_KERNEL);
+ if (!tcd)
+ return -ENOMEM;
+
+ /* Alloc descriptors for this channel */
+ for (i = 0; i  < MPC_DMA_DESCRIPTORS; i++) {
+ mdesc = kzalloc(sizeof(struct mpc_dma_desc), GFP_KERNEL);
+ if (!mdesc) {
+ dev_notice(mdma- >dma.dev, "Memory allocation error. "
+ "Allocated only %u descriptors\n", i);
+ break;
+ }
+
+ dma_async_tx_descriptor_init(&mdesc- >desc, chan);
+ mdesc- >desc.flags = DMA_CTRL_ACK;
+ mdesc- >desc.tx_submit = mpc_dma_tx_submit;
+
+ mdesc- >tcd = &tcd[i];
+ mdesc- >tcd_paddr = tcd_paddr + (i * sizeof(struct mpc_dma_tcd));
+
+ list_add_tail(&mdesc- >node, &descs);
+ }
+
+ /* Return error only if no descriptors were allocated */
+ if (i == 0) {
+ dma_free_coherent(mdma- >dma.dev,
+ MPC_DMA_DESCRIPTORS * sizeof(struct mpc_dma_tcd),
+ tcd, tcd_paddr);
+ return -ENOMEM;
+ }
+
+ spin_lock_irqsave(&mchan- >lock, flags);
+ mchan- >tcd = tcd;
+ mchan- >tcd_paddr = tcd_paddr;
+ list_splice_tail_init(&descs, &mchan- >free);
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ /* Enable Error Interrupt */
+ out_8(&mdma- >regs- >dmaseei, chan- >chan_id);
+
+ return 0;
+}
+
+/* Free channel resources */
+static void mpc_dma_free_chan_resources(struct dma_chan *chan)
+{
+ struct mpc_dma *mdma = dma_chan_to_mpc_dma(chan);
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan);
+ struct mpc_dma_desc *mdesc, *tmp;
+ struct mpc_dma_tcd *tcd;
+ dma_addr_t tcd_paddr;
+ unsigned long flags;
+ LIST_HEAD(descs);
+
+ spin_lock_irqsave(&mchan- >lock, flags);
+
+ /* Channel must be idle */
+ BUG_ON(!list_empty(&mchan- >prepared));
+ BUG_ON(!list_empty(&mchan- >queued));
+ BUG_ON(!list_empty(&mchan- >active));
+ BUG_ON(!list_empty(&mchan- >completed));
+
+ /* Move data */
+ list_splice_tail_init(&mchan- >free, &descs);
+ tcd = mchan- >tcd;
+ tcd_paddr = mchan- >tcd_paddr;
+
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ /* Free DMA memory used by descriptors */
+ dma_free_coherent(mdma- >dma.dev,
+ MPC_DMA_DESCRIPTORS * sizeof(struct mpc_dma_tcd),
+ tcd, tcd_paddr);
+
+ /* Free descriptors */
+ list_for_each_entry_safe(mdesc, tmp, &descs, node)
+ kfree(mdesc);
+
+ /* Disable Error Interrupt */
+ out_8(&mdma- >regs- >dmaceei, chan- >chan_id);
+}
+
+/* Send all pending descriptor to hardware */
+static void mpc_dma_issue_pending(struct dma_chan *chan)
+{
+ /*
+  * We are posting descriptors to the hardware as soon as
+  * they are ready, so this function does nothing.
+  */
+}
+
+/* Check request completion status */
+static enum dma_status
+mpc_dma_is_tx_complete(struct dma_chan *chan, dma_cookie_t cookie,
+ dma_cookie_t *done, dma_cookie_t *used)
+{
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan);
+ unsigned long flags;
+ dma_cookie_t last_used;
+ dma_cookie_t last_complete;
+
+ spin_lock_irqsave(&mchan- >lock, flags);
+ last_used = mchan- >chan.cookie;
+ last_complete = mchan- >completed_cookie;
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ if (done)
+ *done = last_complete;
+
+ if (used)
+ *used = last_used;
+
+ return dma_async_is_complete(cookie, last_complete, last_used);
+}
+
+/* Prepare descriptor for memory to memory copy */
+static struct dma_async_tx_descriptor *
+mpc_dma_prep_memcpy(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
+ size_t len, unsigned long flags)
+{
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan);
+ struct mpc_dma_desc *mdesc = NULL;
+ struct mpc_dma_tcd *tcd;
+ unsigned long iflags;
+
+ /* Get free descriptor */
+ spin_lock_irqsave(&mchan- >lock, iflags);
+ if (!list_empty(&mchan- >free)) {
+ mdesc = list_first_entry(&mchan- >free, struct mpc_dma_desc,
+ node);
+ list_del(&mdesc- >node);
+ }
+ spin_unlock_irqrestore(&mchan- >lock, iflags);
+
+ if (!mdesc)
+ return NULL;
+
+ mdesc- >error = 0;
+ tcd = mdesc- >tcd;
+
+ /* Prepare Transfer Control Descriptor for this transaction */
+ memset(tcd, 0, sizeof(struct mpc_dma_tcd));
+
+ if (IS_ALIGNED(src | dst | len, 32)) {
+ tcd- >ssize = MPC_DMA_TSIZE_32;
+ tcd- >dsize = MPC_DMA_TSIZE_32;
+ tcd- >soff = 32;
+ tcd- >doff = 32;
+ } else if (IS_ALIGNED(src | dst | len, 16)) {
+ tcd- >ssize = MPC_DMA_TSIZE_16;
+ tcd- >dsize = MPC_DMA_TSIZE_16;
+ tcd- >soff = 16;
+ tcd- >doff = 16;
+ } else if (IS_ALIGNED(src | dst | len, 4)) {
+ tcd- >ssize = MPC_DMA_TSIZE_4;
+ tcd- >dsize = MPC_DMA_TSIZE_4;
+ tcd- >soff = 4;
+ tcd- >doff = 4;
+ } else if (IS_ALIGNED(src | dst | len, 2)) {
+ tcd- >ssize = MPC_DMA_TSIZE_2;
+ tcd- >dsize = MPC_DMA_TSIZE_2;
+ tcd- >soff = 2;
+ tcd- >doff = 2;
+ } else {
+ tcd- >ssize = MPC_DMA_TSIZE_1;
+ tcd- >dsize = MPC_DMA_TSIZE_1;
+ tcd- >soff = 1;
+ tcd- >doff = 1;
+ }
+
+ tcd- >saddr = src;
+ tcd- >daddr = dst;
+ tcd- >nbytes = len;
+ tcd- >biter = 1;
+ tcd- >citer = 1;
+
+ /* Place descriptor in prepared list */
+ spin_lock_irqsave(&mchan- >lock, iflags);
+ list_add_tail(&mdesc- >node, &mchan- >prepared);
+ spin_unlock_irqrestore(&mchan- >lock, iflags);
+
+ return &mdesc- >desc;
+}
+
+static int __init mpc_dma_probe(struct of_device *op,
+ const struct of_device_id *match)
+{
+ struct device_node *dn = op- >node;
+ struct device *dev = &op- >dev;
+ struct dma_device *dma;
+ struct mpc_dma *mdma;
+ struct mpc_dma_chan *mchan;
+ struct resource res;
+ ulong regs_start, regs_size;
+ int retval, i;
+
+ mdma = devm_kzalloc(dev, sizeof(struct mpc_dma), GFP_KERNEL);
+ if (!mdma) {
+ dev_err(dev, "Memory exhausted!\n");
+ return -ENOMEM;
+ }
+
+ mdma- >irq = irq_of_parse_and_map(dn, 0);
+ if (mdma- >irq == NO_IRQ) {
+ dev_err(dev, "Error mapping IRQ!\n");
+ return -EINVAL;
+ }
+
+ retval = of_address_to_resource(dn, 0, &res);
+ if (retval) {
+ dev_err(dev, "Error parsing memory region!\n");
+ return retval;
+ }
+
+ regs_start = res.start;
+ regs_size = res.end - res.start + 1;
+
+ if (!devm_request_mem_region(dev, regs_start, regs_size, DRV_NAME)) {
+ dev_err(dev, "Error requesting memory region!\n");
+ return -EBUSY;
+ }
+
+ mdma- >regs = devm_ioremap(dev, regs_start, regs_size);
+ if (!mdma- >regs) {
+ dev_err(dev, "Error mapping memory region!\n");
+ return -ENOMEM;
+ }
+
+ mdma- >tcd = (struct mpc_dma_tcd *)((u8 *)(mdma- >regs)
+ + MPC_DMA_TCD_OFFSET);
+
+ retval = devm_request_irq(dev, mdma- >irq, &mpc_dma_irq, 0, DRV_NAME,
+ mdma);
+ if (retval) {
+ dev_err(dev, "Error requesting IRQ!\n");
+ return -EINVAL;
+ }
+
+ spin_lock_init(&mdma- >error_status_lock);
+
+ dma = &mdma- >dma;
+ dma- >dev = dev;
+ dma- >chancnt = MPC_DMA_CHANNELS;
+ dma- >device_alloc_chan_resources = mpc_dma_alloc_chan_resources;
+ dma- >device_free_chan_resources = mpc_dma_free_chan_resources;
+ dma- >device_issue_pending = mpc_dma_issue_pending;
+ dma- >device_is_tx_complete = mpc_dma_is_tx_complete;
+ dma- >device_prep_dma_memcpy = mpc_dma_prep_memcpy;
+
+ INIT_LIST_HEAD(&dma- >channels);
+ dma_cap_set(DMA_MEMCPY, dma- >cap_mask);
+
+ for (i = 0; i  < dma- >chancnt; i++) {
+ mchan = &mdma- >channels[i];
+
+ mchan- >chan.device = dma;
+ mchan- >chan.chan_id = i;
+ mchan- >chan.cookie = 1;
+ mchan- >completed_cookie = mchan- >chan.cookie;
+
+ INIT_LIST_HEAD(&mchan- >free);
+ INIT_LIST_HEAD(&mchan- >prepared);
+ INIT_LIST_HEAD(&mchan- >queued);
+ INIT_LIST_HEAD(&mchan- >active);
+ INIT_LIST_HEAD(&mchan- >completed);
+
+ spin_lock_init(&mchan- >lock);
+ list_add_tail(&mchan- >chan.device_node, &dma- >channels);
+ }
+
+ tasklet_init(&mdma- >tasklet, mpc_dma_tasklet, (unsigned long)mdma);
+
+ /*
+  * Configure DMA Engine:
+  * - Dynamic clock,
+  * - Round-robin group arbitration,
+  * - Round-robin channel arbitration.
+  */
+ out_be32(&mdma- >regs- >dmacr, MPC_DMA_DMACR_EDCG |
+ MPC_DMA_DMACR_ERGA | MPC_DMA_DMACR_ERCA);
+
+ /* Disable hardware DMA requests */
+ out_be32(&mdma- >regs- >dmaerqh, 0);
+ out_be32(&mdma- >regs- >dmaerql, 0);
+
+ /* Disable error interrupts */
+ out_be32(&mdma- >regs- >dmaeeih, 0);
+ out_be32(&mdma- >regs- >dmaeeil, 0);
+
+ /* Clear interrupts status */
+ out_be32(&mdma- >regs- >dmainth, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaintl, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaerrh, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaerrl, 0xFFFFFFFF);
+
+ /* Route interrupts to IPIC */
+ out_be32(&mdma- >regs- >dmaihsa, 0);
+ out_be32(&mdma- >regs- >dmailsa, 0);
+
+ /* Register DMA engine */
+ dev_set_drvdata(dev, mdma);
+ retval = dma_async_device_register(dma);
+ if (retval)
+ devm_free_irq(dev, mdma- >irq, mdma);
+
+ return retval;
+}
+
+static void __exit mpc_dma_remove(struct of_device *op)
+{
+ struct device *dev = &op- >dev;
+ struct mpc_dma *mdma = dev_get_drvdata(dev);
+
+ devm_free_irq(dev, mdma- >irq, mdma);
+}
+
+static struct of_device_id mpc_dma_match[] = {
+ { .compatible = "fsl,mpc512x-dma", },
+ {},
+};
+
+static struct of_platform_driver mpc_dma_driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .match_table = mpc_dma_match,
+ .probe = mpc_dma_probe,
+ .remove = __exit_p(mpc_dma_remove),
+ .suspend = NULL,
+ .resume = NULL,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mpc_dma_init(void)
+{
+ return of_register_platform_driver(&mpc_dma_driver);
+}
+
+static void __exit mpc_dma_exit(void)
+{
+ of_unregister_platform_driver(&mpc_dma_driver);
+}
+
+module_init(mpc_dma_init);
+module_exit(mpc_dma_exit);
+
+/* MODULE API */
+MODULE_VERSION("0.5");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Piotr Ziecik  <kosmo@semihalf.com >");
diff --git a/drivers/dma/mpc512x_dma.h b/drivers/dma/mpc512x_dma.h
new file mode 100644
index 0000000..867415b
--- /dev/null
+++ b/drivers/dma/mpc512x_dma.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) Freescale Semicondutor, Inc. 2007, 2008.
+ * Copyright (C) Semihalf, 2009.
+ *
+ * Written by Piotr Ziecik  <kosmo@semihalf.com >. Hardware description
+ * (defines, structures and comments) was taken from MPC5121 DMA driver
+ * written by Hongjun Chen  <hong-jun.chen@freescale.com >.
+ *
+ * Approved as OSADL project by a majority of OSADL members and funded
+ * by OSADL membership fees in 2009;  for details see www.osadl.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 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., 59
+ * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * The full GNU General Public License is included in this distribution in the
+ * file called COPYING.
+ */
+#ifndef _MPC512X_DMA_H_
+#define _MPC512X_DMA_H_
+
+#include  <linux/dmaengine.h >
+
+/* Number of DMA Transfer descriptors allocated per channel */
+#define MPC_DMA_DESCRIPTORS 64
+
+/* Macro definitions */
+#define MPC_DMA_CHANNELS 64
+#define MPC_DMA_TCD_OFFSET 0x1000
+
+/* Arbitration mode of group and channel */
+#define MPC_DMA_DMACR_EDCG (1  < < 31)
+#define MPC_DMA_DMACR_ERGA (1  < < 3)
+#define MPC_DMA_DMACR_ERCA (1  < < 2)
+
+/* Error codes */
+#define MPC_DMA_DMAES_VLD (1  < < 31)
+#define MPC_DMA_DMAES_GPE (1  < < 15)
+#define MPC_DMA_DMAES_CPE (1  < < 14)
+#define MPC_DMA_DMAES_ERRCHN(err) \
+ (((err)  > > 8) & 0x3f)
+#define MPC_DMA_DMAES_SAE (1  < < 7)
+#define MPC_DMA_DMAES_SOE (1  < < 6)
+#define MPC_DMA_DMAES_DAE (1  < < 5)
+#define MPC_DMA_DMAES_DOE (1  < < 4)
+#define MPC_DMA_DMAES_NCE (1  < < 3)
+#define MPC_DMA_DMAES_SGE (1  < < 2)
+#define MPC_DMA_DMAES_SBE (1  < < 1)
+#define MPC_DMA_DMAES_DBE (1  < < 0)
+
+#define MPC_DMA_TSIZE_1 0x00
+#define MPC_DMA_TSIZE_2 0x01
+#define MPC_DMA_TSIZE_4 0x02
+#define MPC_DMA_TSIZE_16 0x04
+#define MPC_DMA_TSIZE_32 0x05
+
+/* MPC5121 DMA engine registers */
+struct __attribute__ ((__packed__)) mpc_dma_regs {
+ /* 0x00 */
+ u32 dmacr; /* DMA control register */
+ u32 dmaes; /* DMA error status */
+ /* 0x08 */
+ u32 dmaerqh; /* DMA enable request high(channels 63~32) */
+ u32 dmaerql; /* DMA enable request low(channels 31~0) */
+ u32 dmaeeih; /* DMA enable error interrupt high(ch63~32) */
+ u32 dmaeeil; /* DMA enable error interrupt low(ch31~0) */
+ /* 0x18 */
+ u8 dmaserq; /* DMA set enable request */
+ u8 dmacerq; /* DMA clear enable request */
+ u8 dmaseei; /* DMA set enable error interrupt */
+ u8 dmaceei; /* DMA clear enable error interrupt */
+ /* 0x1c */
+ u8 dmacint; /* DMA clear interrupt request */
+ u8 dmacerr; /* DMA clear error */
+ u8 dmassrt; /* DMA set start bit */
+ u8 dmacdne; /* DMA clear DONE status bit */
+ /* 0x20 */
+ u32 dmainth; /* DMA interrupt request high(ch63~32) */
+ u32 dmaintl; /* DMA interrupt request low(ch31~0) */
+ u32 dmaerrh; /* DMA error high(ch63~32) */
+ u32 dmaerrl; /* DMA error low(ch31~0) */
+ /* 0x30 */
+ u32 dmahrsh; /* DMA hw request status high(ch63~32) */
+ u32 dmahrsl; /* DMA hardware request status low(ch31~0) */
+ u32 dmaihsa; /* DMA interrupt high select AXE(ch63~32) */
+ u32 dmailsa; /* DMA interrupt low select AXE(ch31~0) */
+ /* 0x40 ~ 0xff */
+ u32 reserve0[48]; /* Reserved */
+ /* 0x100 */
+ u8 dchpri[MPC_DMA_CHANNELS];
+ /* DMA channels(0~63) priority */
+};
+
+struct __attribute__ ((__packed__)) mpc_dma_tcd {
+ /* 0x00 */
+ u32 saddr; /* Source address */
+
+ u32 smod:5; /* Source address modulo */
+ u32 ssize:3; /* Source data transfer size */
+ u32 dmod:5; /* Destination address modulo */
+ u32 dsize:3; /* Destination data transfer size */
+ u32 soff:16; /* Signed source address offset */
+
+ /* 0x08 */
+ u32 nbytes; /* Inner "minor" byte count */
+ u32 slast; /* Last source address adjustment */
+ u32 daddr; /* Destination address */
+
+ /* 0x14 */
+ u32 citer_elink:1; /* Enable channel-to-channel linking on
+  * minor loop complete
+  */
+ u32 citer_linkch:6; /* Link channel for minor loop complete */
+ u32 citer:9; /* Current "major" iteration count */
+ u32 doff:16; /* Signed destination address offset */
+
+ /* 0x18 */
+ u32 dlast_sga; /* Last Destination address adjustment/scatter
+  * gather address
+  */
+
+ /* 0x1c */
+ u32 biter_elink:1; /* Enable channel-to-channel linking on major
+  * loop complete
+  */
+ u32 biter_linkch:6;
+ u32 biter:9; /* Beginning "major" iteration count */
+ u32 bwc:2; /* Bandwidth control */
+ u32 major_linkch:6; /* Link channel number */
+ u32 done:1; /* Channel done */
+ u32 active:1; /* Channel active */
+ u32 major_elink:1; /* Enable channel-to-channel linking on major
+  * loop complete
+  */
+ u32 e_sg:1; /* Enable scatter/gather processing */
+ u32 d_req:1; /* Disable request */
+ u32 int_half:1; /* Enable an interrupt when major counter is
+  * half complete
+  */
+ u32 int_maj:1; /* Enable an interrupt when major iteration
+  * count completes
+  */
+ u32 start:1; /* Channel start */
+};
+
+struct mpc_dma_desc {
+ struct dma_async_tx_descriptor desc;
+ struct mpc_dma_tcd *tcd;
+ dma_addr_t tcd_paddr;
+ int error;
+ struct list_head node;
+};
+
+struct mpc_dma_chan {
+ struct dma_chan chan;
+ struct list_head free;
+ struct list_head prepared;
+ struct list_head queued;
+ struct list_head active;
+ struct list_head completed;
+ struct mpc_dma_tcd *tcd;
+ dma_addr_t tcd_paddr;
+ dma_cookie_t completed_cookie;
+
+ /* Lock for this structure */
+ spinlock_t lock;
+};
+
+struct mpc_dma {
+ struct dma_device dma;
+ struct tasklet_struct tasklet;
+ struct mpc_dma_chan channels[MPC_DMA_CHANNELS];
+ struct mpc_dma_regs __iomem *regs;
+ struct mpc_dma_tcd __iomem *tcd;
+ int irq;
+ uint error_status;
+
+ /* Lock for error_status field in this structure */
+ spinlock_t error_status_lock;
+};
+
+#endif /* _MPC512X_DMA_H_ */
-- 
1.6.0.6



------------------------------

_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@ozlabs.org
https://ozlabs.org/mailman/listinfo/linuxppc-dev

End of Linuxppc-dev Digest, Vol 57, Issue 42
********************************************

^ permalink raw reply related

* [PATCH v2] drivers/hvc: add missing __devexit_p()
From: Mike Frysinger @ 2009-05-18  7:44 UTC (permalink / raw)
  To: linux-kernel; +Cc: linuxppc-dev
In-Reply-To: <1242632521-15954-1-git-send-email-vapier@gentoo.org>

The remove function uses __devexit, so the .remove assignment needs
__devexit_p() to fix a build error with hotplug disabled.

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
CC: linuxppc-dev@ozlabs.org
---
v2
	- include all hvc files

 drivers/char/hvc_iseries.c |    2 +-
 drivers/char/hvc_vio.c     |    2 +-
 drivers/char/hvcs.c        |    2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/char/hvc_iseries.c b/drivers/char/hvc_iseries.c
index 449727b..936d05b 100644
--- a/drivers/char/hvc_iseries.c
+++ b/drivers/char/hvc_iseries.c
@@ -241,7 +241,7 @@ static int __devexit hvc_vio_remove(struct vio_dev *vdev)
 static struct vio_driver hvc_vio_driver = {
 	.id_table	= hvc_driver_table,
 	.probe		= hvc_vio_probe,
-	.remove		= hvc_vio_remove,
+	.remove		= __devexit_p(hvc_vio_remove),
 	.driver		= {
 		.name	= hvc_driver_name,
 		.owner	= THIS_MODULE,
diff --git a/drivers/char/hvc_vio.c b/drivers/char/hvc_vio.c
index bd62dc8..c72b994 100644
--- a/drivers/char/hvc_vio.c
+++ b/drivers/char/hvc_vio.c
@@ -113,7 +113,7 @@ static int __devexit hvc_vio_remove(struct vio_dev *vdev)
 static struct vio_driver hvc_vio_driver = {
 	.id_table	= hvc_driver_table,
 	.probe		= hvc_vio_probe,
-	.remove		= hvc_vio_remove,
+	.remove		= __devexit_p(hvc_vio_remove),
 	.driver		= {
 		.name	= hvc_driver_name,
 		.owner	= THIS_MODULE,
diff --git a/drivers/char/hvcs.c b/drivers/char/hvcs.c
index c76bccf..2724d62 100644
--- a/drivers/char/hvcs.c
+++ b/drivers/char/hvcs.c
@@ -868,7 +868,7 @@ static int __devexit hvcs_remove(struct vio_dev *dev)
 static struct vio_driver hvcs_vio_driver = {
 	.id_table	= hvcs_driver_table,
 	.probe		= hvcs_probe,
-	.remove		= hvcs_remove,
+	.remove		= __devexit_p(hvcs_remove),
 	.driver		= {
 		.name	= hvcs_driver_name,
 		.owner	= THIS_MODULE,
-- 
1.6.3

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox