* Re: [PATCH] powerpc: thp: Add write barrier after updating the valid bit
From: Aneesh Kumar K.V @ 2014-07-29 6:55 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: linuxppc-dev, paulus
In-Reply-To: <1406066112.22200.28.camel@pasglop>
Benjamin Herrenschmidt <benh@kernel.crashing.org> writes:
> On Wed, 2014-07-23 at 00:23 +0530, Aneesh Kumar K.V wrote:
>> > A better place for this would be right before the last write to the PMD
>> > (that's also clearing BUSY) in __hash_page_thp(). Basically, it's the
>> > normal lock ordering that's missing here, nothing specific to
>> > mark_hpte_slot_valid() but instead, any state relative to the BUSY bit
>> > in the PMD (including the actual hash writes in update_pp etc...)
>> >
>>
>> IIUC updatepp already have required barriers. ie in updatepp we do tlbie
>> which should take care of the ordering right ?
>
> Only if it succeeds but that doesn't matter, I'd rather we get the
> semantics right. The clearing of the busy bit is an unlock, it should
> have the appropriate barriers like it does in other variants of hash
> page.
ok
>>
>> Now the reason i moved that spm_wmb() to mark_hpte_slot_valid was to
>> pair it with smb_rmb() in get_hpte_slot_array().
>
> Which is also probably in the wrong place. Care to explain to me the
> exact relationship ?
We want to make sure for usage like below we don't reorder the load.
if (pmd_trans_huge(*pmdp)){
get_hpte_slot_array(pmdp)
}
-aneesh
^ permalink raw reply
* Re: [PATCH] powerpc: thp: Add write barrier after updating the valid bit
From: Benjamin Herrenschmidt @ 2014-07-29 7:00 UTC (permalink / raw)
To: Aneesh Kumar K.V; +Cc: linuxppc-dev, paulus
In-Reply-To: <8761igu008.fsf@linux.vnet.ibm.com>
On Tue, 2014-07-29 at 12:25 +0530, Aneesh Kumar K.V wrote:
> We want to make sure for usage like below we don't reorder the load.
>
> if (pmd_trans_huge(*pmdp)){
>
> get_hpte_slot_array(pmdp)
> }
Shouldn't we also make sure that we don't have lock set ? (In case it's
in the middle of being updated).
Cheers,
Ben.
^ permalink raw reply
* Re: [PATCH] powerpc: kvm: make the setup of hpte under the protection of KVMPPC_RMAP_LOCK_BIT
From: Benjamin Herrenschmidt @ 2014-07-29 6:57 UTC (permalink / raw)
To: Liu ping fan; +Cc: Paul Mackerras, linuxppc-dev, Alexander Graf, kvm-ppc
In-Reply-To: <CAFgQCTvC1MX8fN26-KHWRMmpN_zk82LX1=RYn3j61PWo7kvwBQ@mail.gmail.com>
On Mon, 2014-07-28 at 15:58 +0800, Liu ping fan wrote:
> Hope I am right. Take the following seq as an example
>
> if (hptep[0] & HPTE_V_VALID) {
> /* HPTE was previously valid, so we need to invalidate it */
> unlock_rmap(rmap);
> hptep[0] |= HPTE_V_ABSENT;
> kvmppc_invalidate_hpte(kvm, hptep, index);
> /* don't lose previous R and C bits */
> r |= hptep[1] & (HPTE_R_R | HPTE_R_C);
> } else {
> kvmppc_add_revmap_chain(kvm, rev, rmap, index, 0);
> }
> ---------------------------------------------> if we try_to_unmap on
> pfn at here, then @r contains a invalid pfn
> hptep[1] = r;
> eieio();
> hptep[0] = hpte[0];
> asm volatile("ptesync" : : : "memory");
If that was the case we would have the same race in kvmppc_do_h_enter().
I think the fact that the HPTE is locked will prevent the race, ie,
HPTE_V_HVLOCK is set until hptep[0] is written to.
If I look at at the unmap case, my understanding is that it uses
kvm_unmap_rmapp() which will also lock the HPTE (try_lock_hpte)
and so shouldn't have a race vs the above code.
Or do you see a race I don't ?
Cheers,
Ben.
> Thx.
> Fan
>
> On Mon, Jul 28, 2014 at 2:42 PM, Benjamin Herrenschmidt
> <benh@kernel.crashing.org> wrote:
> > On Mon, 2014-07-28 at 14:09 +0800, Liu Ping Fan wrote:
> >> In current code, the setup of hpte is under the risk of race with
> >> mmu_notifier_invalidate, i.e we may setup a hpte with a invalid pfn.
> >> Resolve this issue by sync the two actions by KVMPPC_RMAP_LOCK_BIT.
> >
> > Please describe the race you think you see. I'm quite sure both Paul and
> > I went over that code and somewhat convinced ourselves that it was ok
> > but it's possible that we were both wrong :-)
> >
> > Cheers,
> > Ben.
> >
> >> Signed-off-by: Liu Ping Fan <pingfank@linux.vnet.ibm.com>
> >> ---
> >> arch/powerpc/kvm/book3s_64_mmu_hv.c | 15 ++++++++++-----
> >> 1 file changed, 10 insertions(+), 5 deletions(-)
> >>
> >> diff --git a/arch/powerpc/kvm/book3s_64_mmu_hv.c b/arch/powerpc/kvm/book3s_64_mmu_hv.c
> >> index 8056107..e6dcff4 100644
> >> --- a/arch/powerpc/kvm/book3s_64_mmu_hv.c
> >> +++ b/arch/powerpc/kvm/book3s_64_mmu_hv.c
> >> @@ -754,19 +754,24 @@ int kvmppc_book3s_hv_page_fault(struct kvm_run *run, struct kvm_vcpu *vcpu,
> >>
> >> if (hptep[0] & HPTE_V_VALID) {
> >> /* HPTE was previously valid, so we need to invalidate it */
> >> - unlock_rmap(rmap);
> >> hptep[0] |= HPTE_V_ABSENT;
> >> kvmppc_invalidate_hpte(kvm, hptep, index);
> >> /* don't lose previous R and C bits */
> >> r |= hptep[1] & (HPTE_R_R | HPTE_R_C);
> >> +
> >> + hptep[1] = r;
> >> + eieio();
> >> + hptep[0] = hpte[0];
> >> + asm volatile("ptesync" : : : "memory");
> >> + unlock_rmap(rmap);
> >> } else {
> >> + hptep[1] = r;
> >> + eieio();
> >> + hptep[0] = hpte[0];
> >> + asm volatile("ptesync" : : : "memory");
> >> kvmppc_add_revmap_chain(kvm, rev, rmap, index, 0);
> >> }
> >>
> >> - hptep[1] = r;
> >> - eieio();
> >> - hptep[0] = hpte[0];
> >> - asm volatile("ptesync" : : : "memory");
> >> preempt_enable();
> >> if (page && hpte_is_writable(r))
> >> SetPageDirty(page);
> >
> >
^ permalink raw reply
* Re: [PATCH] powerpc: kvm: make the setup of hpte under the protection of KVMPPC_RMAP_LOCK_BIT
From: Liu ping fan @ 2014-07-29 8:07 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: Paul Mackerras, linuxppc-dev, Alexander Graf, kvm-ppc
In-Reply-To: <1406617022.4935.79.camel@pasglop>
On Tue, Jul 29, 2014 at 2:57 PM, Benjamin Herrenschmidt
<benh@kernel.crashing.org> wrote:
> On Mon, 2014-07-28 at 15:58 +0800, Liu ping fan wrote:
>> Hope I am right. Take the following seq as an example
>>
>> if (hptep[0] & HPTE_V_VALID) {
>> /* HPTE was previously valid, so we need to invalidate it */
>> unlock_rmap(rmap);
>> hptep[0] |= HPTE_V_ABSENT;
>> kvmppc_invalidate_hpte(kvm, hptep, index);
>> /* don't lose previous R and C bits */
>> r |= hptep[1] & (HPTE_R_R | HPTE_R_C);
>> } else {
>> kvmppc_add_revmap_chain(kvm, rev, rmap, index, 0);
>> }
>> ---------------------------------------------> if we try_to_unmap on
>> pfn at here, then @r contains a invalid pfn
>> hptep[1] = r;
>> eieio();
>> hptep[0] = hpte[0];
>> asm volatile("ptesync" : : : "memory");
>
> If that was the case we would have the same race in kvmppc_do_h_enter().
>
> I think the fact that the HPTE is locked will prevent the race, ie,
> HPTE_V_HVLOCK is set until hptep[0] is written to.
>
> If I look at at the unmap case, my understanding is that it uses
> kvm_unmap_rmapp() which will also lock the HPTE (try_lock_hpte)
> and so shouldn't have a race vs the above code.
>
Yes, you are right :)
Thx,
Fan
> Or do you see a race I don't ?
>
> Cheers,
> Ben.
>
>> Thx.
>> Fan
>>
>> On Mon, Jul 28, 2014 at 2:42 PM, Benjamin Herrenschmidt
>> <benh@kernel.crashing.org> wrote:
>> > On Mon, 2014-07-28 at 14:09 +0800, Liu Ping Fan wrote:
>> >> In current code, the setup of hpte is under the risk of race with
>> >> mmu_notifier_invalidate, i.e we may setup a hpte with a invalid pfn.
>> >> Resolve this issue by sync the two actions by KVMPPC_RMAP_LOCK_BIT.
>> >
>> > Please describe the race you think you see. I'm quite sure both Paul and
>> > I went over that code and somewhat convinced ourselves that it was ok
>> > but it's possible that we were both wrong :-)
>> >
>> > Cheers,
>> > Ben.
>> >
>> >> Signed-off-by: Liu Ping Fan <pingfank@linux.vnet.ibm.com>
>> >> ---
>> >> arch/powerpc/kvm/book3s_64_mmu_hv.c | 15 ++++++++++-----
>> >> 1 file changed, 10 insertions(+), 5 deletions(-)
>> >>
>> >> diff --git a/arch/powerpc/kvm/book3s_64_mmu_hv.c b/arch/powerpc/kvm/book3s_64_mmu_hv.c
>> >> index 8056107..e6dcff4 100644
>> >> --- a/arch/powerpc/kvm/book3s_64_mmu_hv.c
>> >> +++ b/arch/powerpc/kvm/book3s_64_mmu_hv.c
>> >> @@ -754,19 +754,24 @@ int kvmppc_book3s_hv_page_fault(struct kvm_run *run, struct kvm_vcpu *vcpu,
>> >>
>> >> if (hptep[0] & HPTE_V_VALID) {
>> >> /* HPTE was previously valid, so we need to invalidate it */
>> >> - unlock_rmap(rmap);
>> >> hptep[0] |= HPTE_V_ABSENT;
>> >> kvmppc_invalidate_hpte(kvm, hptep, index);
>> >> /* don't lose previous R and C bits */
>> >> r |= hptep[1] & (HPTE_R_R | HPTE_R_C);
>> >> +
>> >> + hptep[1] = r;
>> >> + eieio();
>> >> + hptep[0] = hpte[0];
>> >> + asm volatile("ptesync" : : : "memory");
>> >> + unlock_rmap(rmap);
>> >> } else {
>> >> + hptep[1] = r;
>> >> + eieio();
>> >> + hptep[0] = hpte[0];
>> >> + asm volatile("ptesync" : : : "memory");
>> >> kvmppc_add_revmap_chain(kvm, rev, rmap, index, 0);
>> >> }
>> >>
>> >> - hptep[1] = r;
>> >> - eieio();
>> >> - hptep[0] = hpte[0];
>> >> - asm volatile("ptesync" : : : "memory");
>> >> preempt_enable();
>> >> if (page && hpte_is_writable(r))
>> >> SetPageDirty(page);
>> >
>> >
>
>
^ permalink raw reply
* Re: [PATCH] devicetree/bindings: Add binding for micron n25q512a memory
From: Mark Rutland @ 2014-07-29 9:24 UTC (permalink / raw)
To: Scott Wood
Cc: Jain Priyanka-B32167, linux-spi@vger.kernel.org,
linuxppc-dev@lists.ozlabs.org, linux-mtd@lists.infradead.org,
devicetree@vger.kernel.org
In-Reply-To: <1404768354.21434.191.camel@snotra.buserror.net>
On Mon, Jul 07, 2014 at 10:25:54PM +0100, Scott Wood wrote:
> On Thu, 2014-07-03 at 23:08 -0500, Jain Priyanka-B32167 wrote:
> >
> > > -----Original Message-----
> > > From: Wood Scott-B07421
> > > Sent: Friday, July 04, 2014 3:40 AM
> > > To: Jain Priyanka-B32167
> > > Cc: devicetree@vger.kernel.org; linuxppc-dev@lists.ozlabs.org; linux-
> > > spi@vger.kernel.org; linux-mtd@lists.infradead.org
> > > Subject: Re: [PATCH] devicetree/bindings: Add binding for micron n25q512a
> > > memory
> > >
> > > On Thu, 2014-07-03 at 15:42 +0530, Priyanka Jain wrote:
> > > > -Micron n25q512a memory is supported by m25p80 driver.
> > > > Add compatible field required to support n25q512a in m25p80.txt -Add
> > > > micron to the vendor-prefixes.txt file
> > > >
> > > > Signed-off-by: Priyanka Jain <Priyanka.Jain@freescale.com>
> > > > ---
> > > > Documentation/devicetree/bindings/mtd/m25p80.txt | 1 +
> > > > .../devicetree/bindings/vendor-prefixes.txt | 1 +
> > > > 2 files changed, 2 insertions(+), 0 deletions(-)
> > >
> > > Why did you send this to the ppc list but not the spi or mtd lists?
> > >
> > [Jain Priyanka-B32167] Sorry, I missed that
> > > I'm having a hard time following the flow of how these SPI devices get
> > > bound -- is the compatible involved at all? I don't see this string
> > > (with vendor prefix included) in the driver. I do see a table that
> > > contains what looks like device IDs. If the device can report its id,
> > > shouldn't we rely on that rather than device tree compatible?
> > >
> > > -Scott
> > [Jain Priyanka-B32167]
> > Spi driver has a check to device name corresponding to device-id and compare to what is passed in dts string.
>
> Please go into more detail. I don't see where the string
> "micron,n25q512a" appears in the current kernel. I do see "n25q512a",
> but how does that compare successfully with the version of the string
> that has a vendor compatible?
The spi code has a hack to not require an of_device_id_table:
of_register_spi_devices calls of_modalias_node, which for a given
"foo,bar" compatible string will set spi->modalias to "bar", by cutting
off everything up to and including the comma. Later, spi_match_id can
match a device with an spi_device_id->name of "bar".
Thus "micron,n25q512a" can match one of the entries already in
drivers/mtd/spi-nor/spi-nor.c.
This is a real pain when searching around.
Thanks,
Mark.
^ permalink raw reply
* [PATCH 0/8] THP fixes for ppc64
From: Aneesh Kumar K.V @ 2014-07-29 9:37 UTC (permalink / raw)
To: benh, paulus, mpe; +Cc: linuxppc-dev, Aneesh Kumar K.V
Hi,
This patch series fixes machine check exception that we observed when using
transparent huge page along with 4k hash pte on power bare metal platform.
Patch "powerpc: mm: Use read barrier when creating real_pte" is not really
related to thp, but was added in the series because it is fixing a related
issue with 4k pte. That patch was not really needed to fix the MCE.
Aneesh Kumar K.V (8):
powerpc: thp: Add write barrier after updating the valid bit
powerpc: thp: don't recompute vsid and ssize in loop on invalidate
powerpc: thp: invalidate old 64K based hash page mapping before insert
of 4k pte
powerpc: thp: Handle combo pages in invalidate
powerpc: thp: inalidate with vpn in loop
powerpc: thp: use ACCESS_ONCE when loading pmdp
powerpc: mm: Use read barrier when creating real_pte
powerpc: thp: Add tracepoints to track hugepage invalidate
arch/powerpc/include/asm/machdep.h | 6 +--
arch/powerpc/include/asm/pgtable-ppc64.h | 2 +-
arch/powerpc/include/asm/pte-hash64-64k.h | 27 ++++++++--
arch/powerpc/mm/hash_native_64.c | 40 ++++----------
arch/powerpc/mm/hugepage-hash64.c | 88 +++++++++++++++++++++++++++----
arch/powerpc/mm/pgtable_64.c | 44 ++++++++++------
arch/powerpc/mm/tlb_hash64.c | 6 ++-
arch/powerpc/platforms/pseries/lpar.c | 20 +++----
include/trace/events/thp.h | 88 +++++++++++++++++++++++++++++++
9 files changed, 242 insertions(+), 79 deletions(-)
create mode 100644 include/trace/events/thp.h
-aneesh
^ permalink raw reply
* [PATCH 2/8] powerpc: thp: don't recompute vsid and ssize in loop on invalidate
From: Aneesh Kumar K.V @ 2014-07-29 9:37 UTC (permalink / raw)
To: benh, paulus, mpe; +Cc: linuxppc-dev, Aneesh Kumar K.V
In-Reply-To: <1406626669-31154-1-git-send-email-aneesh.kumar@linux.vnet.ibm.com>
The segment identifier and segment size will remain the same in
the loop, So we can compute it outside. We also change the
hugepage_invalidate interface so that we can use it the later patch
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
---
arch/powerpc/include/asm/machdep.h | 6 +++---
arch/powerpc/mm/hash_native_64.c | 19 +++++--------------
arch/powerpc/mm/pgtable_64.c | 24 ++++++++++++------------
arch/powerpc/platforms/pseries/lpar.c | 20 ++++++--------------
4 files changed, 26 insertions(+), 43 deletions(-)
diff --git a/arch/powerpc/include/asm/machdep.h b/arch/powerpc/include/asm/machdep.h
index f92b0b54e921..8dcb721d03d8 100644
--- a/arch/powerpc/include/asm/machdep.h
+++ b/arch/powerpc/include/asm/machdep.h
@@ -57,10 +57,10 @@ struct machdep_calls {
void (*hpte_removebolted)(unsigned long ea,
int psize, int ssize);
void (*flush_hash_range)(unsigned long number, int local);
- void (*hugepage_invalidate)(struct mm_struct *mm,
+ void (*hugepage_invalidate)(unsigned long vsid,
+ unsigned long addr,
unsigned char *hpte_slot_array,
- unsigned long addr, int psize);
-
+ int psize, int ssize);
/* special for kexec, to be called in real mode, linear mapping is
* destroyed as well */
void (*hpte_clear_all)(void);
diff --git a/arch/powerpc/mm/hash_native_64.c b/arch/powerpc/mm/hash_native_64.c
index cf1d325eae8b..fb89d7695a9a 100644
--- a/arch/powerpc/mm/hash_native_64.c
+++ b/arch/powerpc/mm/hash_native_64.c
@@ -412,18 +412,18 @@ static void native_hpte_invalidate(unsigned long slot, unsigned long vpn,
local_irq_restore(flags);
}
-static void native_hugepage_invalidate(struct mm_struct *mm,
+static void native_hugepage_invalidate(unsigned long vsid,
+ unsigned long addr,
unsigned char *hpte_slot_array,
- unsigned long addr, int psize)
+ int psize, int ssize)
{
- int ssize = 0, i;
- int lock_tlbie;
+ int i, lock_tlbie;
struct hash_pte *hptep;
int actual_psize = MMU_PAGE_16M;
unsigned int max_hpte_count, valid;
unsigned long flags, s_addr = addr;
unsigned long hpte_v, want_v, shift;
- unsigned long hidx, vpn = 0, vsid, hash, slot;
+ unsigned long hidx, vpn = 0, hash, slot;
shift = mmu_psize_defs[psize].shift;
max_hpte_count = 1U << (PMD_SHIFT - shift);
@@ -437,15 +437,6 @@ static void native_hugepage_invalidate(struct mm_struct *mm,
/* get the vpn */
addr = s_addr + (i * (1ul << shift));
- if (!is_kernel_addr(addr)) {
- ssize = user_segment_size(addr);
- vsid = get_vsid(mm->context.id, addr, ssize);
- WARN_ON(vsid == 0);
- } else {
- vsid = get_kernel_vsid(addr, mmu_kernel_ssize);
- ssize = mmu_kernel_ssize;
- }
-
vpn = hpt_vpn(addr, vsid, ssize);
hash = hpt_hash(vpn, shift, ssize);
if (hidx & _PTEIDX_SECONDARY)
diff --git a/arch/powerpc/mm/pgtable_64.c b/arch/powerpc/mm/pgtable_64.c
index f6ce1f111f5b..ac8c0754a4e9 100644
--- a/arch/powerpc/mm/pgtable_64.c
+++ b/arch/powerpc/mm/pgtable_64.c
@@ -745,12 +745,21 @@ void hpte_do_hugepage_flush(struct mm_struct *mm, unsigned long addr,
if (!hpte_slot_array)
return;
- /* get the base page size */
+ /* get the base page size,vsid and segment size */
psize = get_slice_psize(mm, s_addr);
+ if (!is_kernel_addr(s_addr)) {
+ ssize = user_segment_size(s_addr);
+ vsid = get_vsid(mm->context.id, s_addr, ssize);
+ WARN_ON(vsid == 0);
+ } else {
+ vsid = get_kernel_vsid(s_addr, mmu_kernel_ssize);
+ ssize = mmu_kernel_ssize;
+ }
if (ppc_md.hugepage_invalidate)
- return ppc_md.hugepage_invalidate(mm, hpte_slot_array,
- s_addr, psize);
+ return ppc_md.hugepage_invalidate(vsid, s_addr,
+ hpte_slot_array,
+ psize, ssize);
/*
* No bluk hpte removal support, invalidate each entry
*/
@@ -768,15 +777,6 @@ void hpte_do_hugepage_flush(struct mm_struct *mm, unsigned long addr,
/* get the vpn */
addr = s_addr + (i * (1ul << shift));
- if (!is_kernel_addr(addr)) {
- ssize = user_segment_size(addr);
- vsid = get_vsid(mm->context.id, addr, ssize);
- WARN_ON(vsid == 0);
- } else {
- vsid = get_kernel_vsid(addr, mmu_kernel_ssize);
- ssize = mmu_kernel_ssize;
- }
-
vpn = hpt_vpn(addr, vsid, ssize);
hash = hpt_hash(vpn, shift, ssize);
if (hidx & _PTEIDX_SECONDARY)
diff --git a/arch/powerpc/platforms/pseries/lpar.c b/arch/powerpc/platforms/pseries/lpar.c
index b02af9ef3ff6..ccf6f162f69c 100644
--- a/arch/powerpc/platforms/pseries/lpar.c
+++ b/arch/powerpc/platforms/pseries/lpar.c
@@ -430,16 +430,17 @@ static void __pSeries_lpar_hugepage_invalidate(unsigned long *slot,
spin_unlock_irqrestore(&pSeries_lpar_tlbie_lock, flags);
}
-static void pSeries_lpar_hugepage_invalidate(struct mm_struct *mm,
- unsigned char *hpte_slot_array,
- unsigned long addr, int psize)
+static void pSeries_lpar_hugepage_invalidate(unsigned long vsid,
+ unsigned long addr,
+ unsigned char *hpte_slot_array,
+ int psize, int ssize)
{
- int ssize = 0, i, index = 0;
+ int i, index = 0;
unsigned long s_addr = addr;
unsigned int max_hpte_count, valid;
unsigned long vpn_array[PPC64_HUGE_HPTE_BATCH];
unsigned long slot_array[PPC64_HUGE_HPTE_BATCH];
- unsigned long shift, hidx, vpn = 0, vsid, hash, slot;
+ unsigned long shift, hidx, vpn = 0, hash, slot;
shift = mmu_psize_defs[psize].shift;
max_hpte_count = 1U << (PMD_SHIFT - shift);
@@ -452,15 +453,6 @@ static void pSeries_lpar_hugepage_invalidate(struct mm_struct *mm,
/* get the vpn */
addr = s_addr + (i * (1ul << shift));
- if (!is_kernel_addr(addr)) {
- ssize = user_segment_size(addr);
- vsid = get_vsid(mm->context.id, addr, ssize);
- WARN_ON(vsid == 0);
- } else {
- vsid = get_kernel_vsid(addr, mmu_kernel_ssize);
- ssize = mmu_kernel_ssize;
- }
-
vpn = hpt_vpn(addr, vsid, ssize);
hash = hpt_hash(vpn, shift, ssize);
if (hidx & _PTEIDX_SECONDARY)
--
1.9.1
^ permalink raw reply related
* [PATCH 3/8] powerpc: thp: invalidate old 64K based hash page mapping before insert of 4k pte
From: Aneesh Kumar K.V @ 2014-07-29 9:37 UTC (permalink / raw)
To: benh, paulus, mpe; +Cc: linuxppc-dev, Aneesh Kumar K.V
In-Reply-To: <1406626669-31154-1-git-send-email-aneesh.kumar@linux.vnet.ibm.com>
If we changed base page size of the segment, either via sub_page_protect
or via remap_4k_pfn, we do a demote_segment which doesn't flush the hash
table entries. We do a lazy hash page table flush for all mapped pages
in the demoted segment. This happens when we handle hash page fault
for these pages.
We use _PAGE_COMBO bit along with _PAGE_HASHPTE to indicate whether a
pte is backed by 4K hash pte. If we find _PAGE_COMBO not set on the pte,
that implies that we could possibly have older 64K hash pte entries in
the hash page table and we need to invalidate those entries.
Handle this correctly for 16M pages
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
---
arch/powerpc/mm/hugepage-hash64.c | 79 ++++++++++++++++++++++++++++++++++-----
1 file changed, 70 insertions(+), 9 deletions(-)
diff --git a/arch/powerpc/mm/hugepage-hash64.c b/arch/powerpc/mm/hugepage-hash64.c
index 11f9a37ca2c6..1fb609dcc49b 100644
--- a/arch/powerpc/mm/hugepage-hash64.c
+++ b/arch/powerpc/mm/hugepage-hash64.c
@@ -18,6 +18,57 @@
#include <linux/mm.h>
#include <asm/machdep.h>
+static void invalidate_old_hpte(unsigned long vsid, unsigned long addr,
+ pmd_t *pmdp, unsigned int psize, int ssize)
+{
+ int i, max_hpte_count, valid;
+ unsigned long s_addr;
+ unsigned char *hpte_slot_array;
+ unsigned long hidx, shift, vpn, hash, slot;
+
+ s_addr = addr & HPAGE_PMD_MASK;
+ hpte_slot_array = get_hpte_slot_array(pmdp);
+ /*
+ * IF we try to do a HUGE PTE update after a withdraw is done.
+ * we will find the below NULL. This happens when we do
+ * split_huge_page_pmd
+ */
+ if (!hpte_slot_array)
+ return;
+
+ if (ppc_md.hugepage_invalidate)
+ return ppc_md.hugepage_invalidate(vsid, s_addr, hpte_slot_array,
+ psize, ssize);
+ /*
+ * No bluk hpte removal support, invalidate each entry
+ */
+ shift = mmu_psize_defs[psize].shift;
+ max_hpte_count = HPAGE_PMD_SIZE >> shift;
+ for (i = 0; i < max_hpte_count; i++) {
+ /*
+ * 8 bits per each hpte entries
+ * 000| [ secondary group (one bit) | hidx (3 bits) | valid bit]
+ */
+ valid = hpte_valid(hpte_slot_array, i);
+ if (!valid)
+ continue;
+ hidx = hpte_hash_index(hpte_slot_array, i);
+
+ /* get the vpn */
+ addr = s_addr + (i * (1ul << shift));
+ vpn = hpt_vpn(addr, vsid, ssize);
+ hash = hpt_hash(vpn, shift, ssize);
+ if (hidx & _PTEIDX_SECONDARY)
+ hash = ~hash;
+
+ slot = (hash & htab_hash_mask) * HPTES_PER_GROUP;
+ slot += hidx & _PTEIDX_GROUP_IX;
+ ppc_md.hpte_invalidate(slot, vpn, psize,
+ MMU_PAGE_16M, ssize, 0);
+ }
+}
+
+
int __hash_page_thp(unsigned long ea, unsigned long access, unsigned long vsid,
pmd_t *pmdp, unsigned long trap, int local, int ssize,
unsigned int psize)
@@ -85,6 +136,15 @@ int __hash_page_thp(unsigned long ea, unsigned long access, unsigned long vsid,
vpn = hpt_vpn(ea, vsid, ssize);
hash = hpt_hash(vpn, shift, ssize);
hpte_slot_array = get_hpte_slot_array(pmdp);
+ if (psize == MMU_PAGE_4K) {
+ /*
+ * invalidate the old hpte entry if we have that mapped via 64K
+ * base page size. This is because demote_segment won't flush
+ * hash page table entries.
+ */
+ if ((old_pmd & _PAGE_HASHPTE) && !(old_pmd & _PAGE_COMBO))
+ invalidate_old_hpte(vsid, ea, pmdp, MMU_PAGE_64K, ssize);
+ }
valid = hpte_valid(hpte_slot_array, index);
if (valid) {
@@ -107,11 +167,8 @@ int __hash_page_thp(unsigned long ea, unsigned long access, unsigned long vsid,
* safely update this here.
*/
valid = 0;
- new_pmd &= ~_PAGE_HPTEFLAGS;
hpte_slot_array[index] = 0;
- } else
- /* clear the busy bits and set the hash pte bits */
- new_pmd = (new_pmd & ~_PAGE_HPTEFLAGS) | _PAGE_HASHPTE;
+ }
}
if (!valid) {
@@ -119,11 +176,7 @@ int __hash_page_thp(unsigned long ea, unsigned long access, unsigned long vsid,
/* insert new entry */
pa = pmd_pfn(__pmd(old_pmd)) << PAGE_SHIFT;
-repeat:
- hpte_group = ((hash & htab_hash_mask) * HPTES_PER_GROUP) & ~0x7UL;
-
- /* clear the busy bits and set the hash pte bits */
- new_pmd = (new_pmd & ~_PAGE_HPTEFLAGS) | _PAGE_HASHPTE;
+ new_pmd |= _PAGE_HASHPTE;
/* Add in WIMG bits */
rflags |= (new_pmd & (_PAGE_WRITETHRU | _PAGE_NO_CACHE |
@@ -132,6 +185,8 @@ repeat:
* enable the memory coherence always
*/
rflags |= HPTE_R_M;
+repeat:
+ hpte_group = ((hash & htab_hash_mask) * HPTES_PER_GROUP) & ~0x7UL;
/* Insert into the hash table, primary slot */
slot = ppc_md.hpte_insert(hpte_group, vpn, pa, rflags, 0,
@@ -172,6 +227,12 @@ repeat:
mark_hpte_slot_valid(hpte_slot_array, index, slot);
}
/*
+ * Mark the pte with _PAGE_COMBO, if we are trying to hash it with
+ * base page size 4k.
+ */
+ if (psize == MMU_PAGE_4K)
+ new_pmd |= _PAGE_COMBO;
+ /*
* The hpte valid is stored in the pgtable whose address is in the
* second half of the PMD. Order this against clearing of the busy bit in
* huge pmd.
--
1.9.1
^ permalink raw reply related
* [PATCH 4/8] powerpc: thp: Handle combo pages in invalidate
From: Aneesh Kumar K.V @ 2014-07-29 9:37 UTC (permalink / raw)
To: benh, paulus, mpe; +Cc: linuxppc-dev, Aneesh Kumar K.V
In-Reply-To: <1406626669-31154-1-git-send-email-aneesh.kumar@linux.vnet.ibm.com>
If we changed base page size of the segment, either via sub_page_protect
or via remap_4k_pfn, we do a demote_segment which doesn't flush the hash
table entries. We do a lazy hash page table flush for all mapped pages
in the demoted segment. This happens when we handle hash page fault for
these pages.
We use _PAGE_COMBO bit along with _PAGE_HASHPTE to indicate whether a
pte is backed by 4K hash pte. If we find _PAGE_COMBO not set on the pte,
that implies that we could possibly have older 64K hash pte entries in
the hash page table and we need to invalidate those entries.
Use _PAGE_COMBO to determine the page size with which we should
invalidate the hash table entries on unmap.
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
---
arch/powerpc/include/asm/pgtable-ppc64.h | 2 +-
arch/powerpc/mm/pgtable_64.c | 14 +++++++++++---
arch/powerpc/mm/tlb_hash64.c | 2 +-
3 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/arch/powerpc/include/asm/pgtable-ppc64.h b/arch/powerpc/include/asm/pgtable-ppc64.h
index eb9261024f51..7b3d54fae46f 100644
--- a/arch/powerpc/include/asm/pgtable-ppc64.h
+++ b/arch/powerpc/include/asm/pgtable-ppc64.h
@@ -413,7 +413,7 @@ static inline char *get_hpte_slot_array(pmd_t *pmdp)
}
extern void hpte_do_hugepage_flush(struct mm_struct *mm, unsigned long addr,
- pmd_t *pmdp);
+ pmd_t *pmdp, unsigned long old_pmd);
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
extern pmd_t pfn_pmd(unsigned long pfn, pgprot_t pgprot);
extern pmd_t mk_pmd(struct page *page, pgprot_t pgprot);
diff --git a/arch/powerpc/mm/pgtable_64.c b/arch/powerpc/mm/pgtable_64.c
index ac8c0754a4e9..71d084b6f766 100644
--- a/arch/powerpc/mm/pgtable_64.c
+++ b/arch/powerpc/mm/pgtable_64.c
@@ -538,7 +538,7 @@ unsigned long pmd_hugepage_update(struct mm_struct *mm, unsigned long addr,
*pmdp = __pmd((old & ~clr) | set);
#endif
if (old & _PAGE_HASHPTE)
- hpte_do_hugepage_flush(mm, addr, pmdp);
+ hpte_do_hugepage_flush(mm, addr, pmdp, old);
return old;
}
@@ -645,7 +645,7 @@ void pmdp_splitting_flush(struct vm_area_struct *vma,
if (!(old & _PAGE_SPLITTING)) {
/* We need to flush the hpte */
if (old & _PAGE_HASHPTE)
- hpte_do_hugepage_flush(vma->vm_mm, address, pmdp);
+ hpte_do_hugepage_flush(vma->vm_mm, address, pmdp, old);
}
/*
* This ensures that generic code that rely on IRQ disabling
@@ -723,7 +723,7 @@ void pmdp_invalidate(struct vm_area_struct *vma, unsigned long address,
* neesd to be flushed.
*/
void hpte_do_hugepage_flush(struct mm_struct *mm, unsigned long addr,
- pmd_t *pmdp)
+ pmd_t *pmdp, unsigned long old_pmd)
{
int ssize, i;
unsigned long s_addr;
@@ -746,7 +746,15 @@ void hpte_do_hugepage_flush(struct mm_struct *mm, unsigned long addr,
return;
/* get the base page size,vsid and segment size */
+#ifdef CONFIG_DEBUG_VM
psize = get_slice_psize(mm, s_addr);
+ BUG_ON(psize == MMU_PAGE_16M);
+#endif
+ if (old_pmd & _PAGE_COMBO)
+ psize = MMU_PAGE_4K;
+ else
+ psize = MMU_PAGE_64K;
+
if (!is_kernel_addr(s_addr)) {
ssize = user_segment_size(s_addr);
vsid = get_vsid(mm->context.id, s_addr, ssize);
diff --git a/arch/powerpc/mm/tlb_hash64.c b/arch/powerpc/mm/tlb_hash64.c
index c99f6510a0b2..9adda5790463 100644
--- a/arch/powerpc/mm/tlb_hash64.c
+++ b/arch/powerpc/mm/tlb_hash64.c
@@ -216,7 +216,7 @@ void __flush_hash_table_range(struct mm_struct *mm, unsigned long start,
if (!(pte & _PAGE_HASHPTE))
continue;
if (unlikely(hugepage_shift && pmd_trans_huge(*(pmd_t *)pte)))
- hpte_do_hugepage_flush(mm, start, (pmd_t *)pte);
+ hpte_do_hugepage_flush(mm, start, (pmd_t *)ptep, pte);
else
hpte_need_flush(mm, start, ptep, pte, 0);
}
--
1.9.1
^ permalink raw reply related
* [PATCH 5/8] powerpc: thp: inalidate with vpn in loop
From: Aneesh Kumar K.V @ 2014-07-29 9:37 UTC (permalink / raw)
To: benh, paulus, mpe; +Cc: linuxppc-dev, Aneesh Kumar K.V
In-Reply-To: <1406626669-31154-1-git-send-email-aneesh.kumar@linux.vnet.ibm.com>
As per ISA, for 4k base page size we compare 14..65 bits of VA specified
with the entry_VA in tlb. That implies we need to make sure we do a
tlbie with all the possible 4k va we used to access the 16MB hugepage.
With 64k base page size we compare 14..57 bits of VA. Hence we cannot
ignore the lower 24 bits of va while tlbie .We also cannot tlb
invalidate a 16MB entry with just one tlbie instruction because
we don't track which va was used to instantiate the tlb entry.
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
---
arch/powerpc/mm/hash_native_64.c | 23 +++++++----------------
1 file changed, 7 insertions(+), 16 deletions(-)
diff --git a/arch/powerpc/mm/hash_native_64.c b/arch/powerpc/mm/hash_native_64.c
index fb89d7695a9a..afc0a8295f84 100644
--- a/arch/powerpc/mm/hash_native_64.c
+++ b/arch/powerpc/mm/hash_native_64.c
@@ -417,7 +417,7 @@ static void native_hugepage_invalidate(unsigned long vsid,
unsigned char *hpte_slot_array,
int psize, int ssize)
{
- int i, lock_tlbie;
+ int i;
struct hash_pte *hptep;
int actual_psize = MMU_PAGE_16M;
unsigned int max_hpte_count, valid;
@@ -456,22 +456,13 @@ static void native_hugepage_invalidate(unsigned long vsid,
else
/* Invalidate the hpte. NOTE: this also unlocks it */
hptep->v = 0;
+ /*
+ * We need to do tlb invalidate for all the address, tlbie
+ * instruction compares entry_VA in tlb with the VA specified
+ * here
+ */
+ tlbie(vpn, psize, actual_psize, ssize, 0);
}
- /*
- * Since this is a hugepage, we just need a single tlbie.
- * use the last vpn.
- */
- lock_tlbie = !mmu_has_feature(MMU_FTR_LOCKLESS_TLBIE);
- if (lock_tlbie)
- raw_spin_lock(&native_tlbie_lock);
-
- asm volatile("ptesync":::"memory");
- __tlbie(vpn, psize, actual_psize, ssize);
- asm volatile("eieio; tlbsync; ptesync":::"memory");
-
- if (lock_tlbie)
- raw_spin_unlock(&native_tlbie_lock);
-
local_irq_restore(flags);
}
--
1.9.1
^ permalink raw reply related
* [PATCH 6/8] powerpc: thp: use ACCESS_ONCE when loading pmdp
From: Aneesh Kumar K.V @ 2014-07-29 9:37 UTC (permalink / raw)
To: benh, paulus, mpe; +Cc: linuxppc-dev, Aneesh Kumar K.V
In-Reply-To: <1406626669-31154-1-git-send-email-aneesh.kumar@linux.vnet.ibm.com>
We would get wrong results in compiler recomputed old_pmd. Avoid
that by using ACCESS_ONCE
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
---
arch/powerpc/mm/hugepage-hash64.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/mm/hugepage-hash64.c b/arch/powerpc/mm/hugepage-hash64.c
index 1fb609dcc49b..5f5e6328c21c 100644
--- a/arch/powerpc/mm/hugepage-hash64.c
+++ b/arch/powerpc/mm/hugepage-hash64.c
@@ -84,7 +84,9 @@ int __hash_page_thp(unsigned long ea, unsigned long access, unsigned long vsid,
* atomically mark the linux large page PMD busy and dirty
*/
do {
- old_pmd = pmd_val(*pmdp);
+ pmd_t pmd = ACCESS_ONCE(*pmdp);
+
+ old_pmd = pmd_val(pmd);
/* If PMD busy, retry the access */
if (unlikely(old_pmd & _PAGE_BUSY))
return 0;
--
1.9.1
^ permalink raw reply related
* [PATCH 7/8] powerpc: mm: Use read barrier when creating real_pte
From: Aneesh Kumar K.V @ 2014-07-29 9:37 UTC (permalink / raw)
To: benh, paulus, mpe; +Cc: linuxppc-dev, Aneesh Kumar K.V
In-Reply-To: <1406626669-31154-1-git-send-email-aneesh.kumar@linux.vnet.ibm.com>
On ppc64 we support 4K hash pte with 64K page size. That requires
us to track the hash pte slot information on a per 4k basis. We do that
by storing the slot details in the second half of pte page. The pte bit
_PAGE_COMBO is used to indicate whether the second half need to be
looked while building real_pte. We need to use read memory barrier while
doing that so that load of hidx is not reordered w.r.t _PAGE_COMBO
check. On the store side we already do a lwsync in __hash_page_4K
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
---
arch/powerpc/include/asm/pte-hash64-64k.h | 31 ++++++++++++++++++++++++++-----
1 file changed, 26 insertions(+), 5 deletions(-)
diff --git a/arch/powerpc/include/asm/pte-hash64-64k.h b/arch/powerpc/include/asm/pte-hash64-64k.h
index d836d945068d..44a02ccb0c1a 100644
--- a/arch/powerpc/include/asm/pte-hash64-64k.h
+++ b/arch/powerpc/include/asm/pte-hash64-64k.h
@@ -46,11 +46,32 @@
* in order to deal with 64K made of 4K HW pages. Thus we override the
* generic accessors and iterators here
*/
-#define __real_pte(e,p) ((real_pte_t) { \
- (e), (pte_val(e) & _PAGE_COMBO) ? \
- (pte_val(*((p) + PTRS_PER_PTE))) : 0 })
-#define __rpte_to_hidx(r,index) ((pte_val((r).pte) & _PAGE_COMBO) ? \
- (((r).hidx >> ((index)<<2)) & 0xf) : ((pte_val((r).pte) >> 12) & 0xf))
+#define __real_pte __real_pte
+static inline real_pte_t __real_pte(pte_t pte, pte_t *ptep)
+{
+ real_pte_t rpte;
+
+ rpte.pte = pte;
+ rpte.hidx = 0;
+ if (pte_val(pte) & _PAGE_COMBO) {
+ /*
+ * Make sure we order the hidx load against the _PAGE_COMBO
+ * check. The store side ordering is done in __hash_page_4K
+ */
+ smp_rmb();
+ rpte.hidx = pte_val(*((ptep) + PTRS_PER_PTE));
+ }
+ return rpte;
+}
+
+static inline unsigned long __rpte_to_hidx(real_pte_t rpte, unsigned long index)
+{
+ if ((pte_val(rpte.pte) & _PAGE_COMBO))
+ return (rpte.hidx >> (index<<2)) & 0xf;
+ else
+ return (pte_val(rpte.pte) >> 12) & 0xf;
+}
+
#define __rpte_to_pte(r) ((r).pte)
#define __rpte_sub_valid(rpte, index) \
(pte_val(rpte.pte) & (_PAGE_HPTE_SUB0 >> (index)))
--
1.9.1
^ permalink raw reply related
* [PATCH 8/8] powerpc: thp: Add tracepoints to track hugepage invalidate
From: Aneesh Kumar K.V @ 2014-07-29 9:37 UTC (permalink / raw)
To: benh, paulus, mpe; +Cc: linuxppc-dev, Aneesh Kumar K.V
In-Reply-To: <1406626669-31154-1-git-send-email-aneesh.kumar@linux.vnet.ibm.com>
Add tracepoint to track hugepage invalidate. This help us
in debugging difficult to track bugs.
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
---
arch/powerpc/mm/pgtable_64.c | 6 +++
arch/powerpc/mm/tlb_hash64.c | 4 ++
include/trace/events/thp.h | 88 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 98 insertions(+)
create mode 100644 include/trace/events/thp.h
diff --git a/arch/powerpc/mm/pgtable_64.c b/arch/powerpc/mm/pgtable_64.c
index 71d084b6f766..ecc4079ca56c 100644
--- a/arch/powerpc/mm/pgtable_64.c
+++ b/arch/powerpc/mm/pgtable_64.c
@@ -54,6 +54,9 @@
#include "mmu_decl.h"
+#define CREATE_TRACE_POINTS
+#include <trace/events/thp.h>
+
/* Some sanity checking */
#if TASK_SIZE_USER64 > PGTABLE_RANGE
#error TASK_SIZE_USER64 exceeds pagetable range
@@ -537,6 +540,7 @@ unsigned long pmd_hugepage_update(struct mm_struct *mm, unsigned long addr,
old = pmd_val(*pmdp);
*pmdp = __pmd((old & ~clr) | set);
#endif
+ trace_hugepage_update(addr, old, clr, set);
if (old & _PAGE_HASHPTE)
hpte_do_hugepage_flush(mm, addr, pmdp, old);
return old;
@@ -642,6 +646,7 @@ void pmdp_splitting_flush(struct vm_area_struct *vma,
* If we didn't had the splitting flag set, go and flush the
* HPTE entries.
*/
+ trace_hugepage_splitting(address, old);
if (!(old & _PAGE_SPLITTING)) {
/* We need to flush the hpte */
if (old & _PAGE_HASHPTE)
@@ -709,6 +714,7 @@ void set_pmd_at(struct mm_struct *mm, unsigned long addr,
assert_spin_locked(&mm->page_table_lock);
WARN_ON(!pmd_trans_huge(pmd));
#endif
+ trace_hugepage_set_pmd(addr, pmd);
return set_pte_at(mm, addr, pmdp_ptep(pmdp), pmd_pte(pmd));
}
diff --git a/arch/powerpc/mm/tlb_hash64.c b/arch/powerpc/mm/tlb_hash64.c
index 9adda5790463..d2a94b85dbc2 100644
--- a/arch/powerpc/mm/tlb_hash64.c
+++ b/arch/powerpc/mm/tlb_hash64.c
@@ -30,6 +30,8 @@
#include <asm/tlb.h>
#include <asm/bug.h>
+#include <trace/events/thp.h>
+
DEFINE_PER_CPU(struct ppc64_tlb_batch, ppc64_tlb_batch);
/*
@@ -213,6 +215,8 @@ void __flush_hash_table_range(struct mm_struct *mm, unsigned long start,
if (ptep == NULL)
continue;
pte = pte_val(*ptep);
+ if (hugepage_shift)
+ trace_hugepage_invalidate(start, pte_val(pte));
if (!(pte & _PAGE_HASHPTE))
continue;
if (unlikely(hugepage_shift && pmd_trans_huge(*(pmd_t *)pte)))
diff --git a/include/trace/events/thp.h b/include/trace/events/thp.h
new file mode 100644
index 000000000000..b59b065e9e5d
--- /dev/null
+++ b/include/trace/events/thp.h
@@ -0,0 +1,88 @@
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM thp
+
+#if !defined(_TRACE_THP_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_THP_H
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(hugepage_invalidate,
+
+ TP_PROTO(unsigned long addr, unsigned long pte),
+ TP_ARGS(addr, pte),
+ TP_STRUCT__entry(
+ __field(unsigned long, addr)
+ __field(unsigned long, pte)
+ ),
+
+ TP_fast_assign(
+ __entry->addr = addr;
+ __entry->pte = pte;
+ ),
+
+ TP_printk("hugepage invalidate at addr 0x%lx and pte = 0x%lx",
+ __entry->addr, __entry->pte)
+);
+
+TRACE_EVENT(hugepage_set_pmd,
+
+ TP_PROTO(unsigned long addr, unsigned long pmd),
+ TP_ARGS(addr, pmd),
+ TP_STRUCT__entry(
+ __field(unsigned long, addr)
+ __field(unsigned long, pmd)
+ ),
+
+ TP_fast_assign(
+ __entry->addr = addr;
+ __entry->pmd = pmd;
+ ),
+
+ TP_printk("Set pmd with 0x%lx with 0x%lx", __entry->addr, __entry->pmd)
+);
+
+
+TRACE_EVENT(hugepage_update,
+
+ TP_PROTO(unsigned long addr, unsigned long pte, unsigned long clr, unsigned long set),
+ TP_ARGS(addr, pte, clr, set),
+ TP_STRUCT__entry(
+ __field(unsigned long, addr)
+ __field(unsigned long, pte)
+ __field(unsigned long, clr)
+ __field(unsigned long, set)
+ ),
+
+ TP_fast_assign(
+ __entry->addr = addr;
+ __entry->pte = pte;
+ __entry->clr = clr;
+ __entry->set = set;
+
+ ),
+
+ TP_printk("hugepage update at addr 0x%lx and pte = 0x%lx clr = 0x%lx, set = 0x%lx", __entry->addr, __entry->pte, __entry->clr, __entry->set)
+);
+TRACE_EVENT(hugepage_splitting,
+
+ TP_PROTO(unsigned long addr, unsigned long pte),
+ TP_ARGS(addr, pte),
+ TP_STRUCT__entry(
+ __field(unsigned long, addr)
+ __field(unsigned long, pte)
+ ),
+
+ TP_fast_assign(
+ __entry->addr = addr;
+ __entry->pte = pte;
+ ),
+
+ TP_printk("hugepage splitting at addr 0x%lx and pte = 0x%lx",
+ __entry->addr, __entry->pte)
+);
+
+#endif /* _TRACE_THP_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
--
1.9.1
^ permalink raw reply related
* [PATCH 1/8] powerpc: thp: Add write barrier after updating the valid bit
From: Aneesh Kumar K.V @ 2014-07-29 9:37 UTC (permalink / raw)
To: benh, paulus, mpe; +Cc: linuxppc-dev, Aneesh Kumar K.V
In-Reply-To: <1406626669-31154-1-git-send-email-aneesh.kumar@linux.vnet.ibm.com>
With hugepages, we store the hpte valid information in the pte page
whose address is stored in the second half of the PMD. Use a
write barrier to make sure clearing pmd busy bit and updating
hpte valid info are ordered properly.
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
---
arch/powerpc/mm/hugepage-hash64.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/mm/hugepage-hash64.c b/arch/powerpc/mm/hugepage-hash64.c
index 826893fcb3a7..11f9a37ca2c6 100644
--- a/arch/powerpc/mm/hugepage-hash64.c
+++ b/arch/powerpc/mm/hugepage-hash64.c
@@ -172,8 +172,11 @@ repeat:
mark_hpte_slot_valid(hpte_slot_array, index, slot);
}
/*
- * No need to use ldarx/stdcx here
+ * The hpte valid is stored in the pgtable whose address is in the
+ * second half of the PMD. Order this against clearing of the busy bit in
+ * huge pmd.
*/
+ smp_wmb();
*pmdp = __pmd(new_pmd & ~_PAGE_BUSY);
return 0;
}
--
1.9.1
^ permalink raw reply related
* Re: [PATCH v3 2/2] ASoC: fsl_asrc: Add ASRC ASoC CPU DAI and platform drivers
From: Mark Rutland @ 2014-07-29 9:46 UTC (permalink / raw)
To: Nicolin Chen
Cc: devicetree@vger.kernel.org, alsa-devel@alsa-project.org,
b42378@freescale.com, timur@tabi.org, Pawel Moll,
ijc+devicetree@hellion.org.uk, tiwai@suse.de,
linux-kernel@vger.kernel.org, rdunlap@infradead.org,
b02247@freescale.com, lgirdwood@gmail.com, robh+dt@kernel.org,
perex@perex.cz, broonie@kernel.org, galak@codeaurora.org,
grant.likely@linaro.org, tklauser@distanz.ch,
shawn.guo@linaro.org, linuxppc-dev@lists.ozlabs.org
In-Reply-To: <f4ff79269a1680f9ce128e0e98fea926d3a41834.1406259578.git.nicoleotsuka@gmail.com>
> + - big-endian : If this property is absent, the native endian mode will
> + be in use as default, or the big endian mode will be in use
> + for all the device registers.
Native endian is meaningless. If a CPU supports both BE and LE, there is
no native endianness. The endianness of the kernel is dynamic while the
endianness of registers in HW is fixed.
Just choose an endianness to assume by default (presumably little). That
way this describes the HW and always works with a kernel of arbitrary
endianness.
Thanks,
Mark.
^ permalink raw reply
* [PATCH v6 0/2] Add Freescale ASRC driver
From: Nicolin Chen @ 2014-07-29 10:08 UTC (permalink / raw)
To: broonie
Cc: mark.rutland, devicetree, alsa-devel, b42378, timur, pawel.moll,
ijc+devicetree, tiwai, linux-kernel, rdunlap, b02247, lgirdwood,
perex, varkabhadram, robh+dt, galak, grant.likely, tklauser,
shawn.guo, linuxppc-dev
This series of patches add Freescale ASRC module driver support along
with the extra request in imx-sdma structure required by SDMA Device
to Device script.
The previous version has been in the maillist for nearly six months
without any comment and reply. So I decide to drop the SDMA part's
change since this series focus on ASRC support (Later, we can send
SDMA part separately.) so that ASRC driver may get upstream first.
Changelog
v6: (Follows Mark Rutland's comments)
* PATCH-2: Revised the big endian part in the binding document.
v5: (All follow Varka's suggestions)
* PATCH-2: Added missing '\n' to pair_err() calls.
* PATCH-2: Return the final regmap_write() for fsl_asrc_init().
* PATCH-2: Dropped fsl_asrc_devtype and use simple ids behind fsl_asrc_pm.
* PATCH-2: Use of_device_is_compatible() to differ compatible directly.
* PATCH-2: Fixed indentation for devm_regmap_init_mmio_clk() call.
v4: (All follow Varka's suggestions)
* PATCH-2: Added '\n' to dev_dbg() inside fsl_asrc_isr().
* PATCH-2: Added indentations to DT binding document.
* PATCH-2: Redefined fsl_asrc_set_watermarks() as void type.
* PATCH-2: Put fsl_asrc_isr() and fsl_asrc_ids before probe()
v3:
* PATCH-1: Added an Acked-by from Shawn Guo
* PATCH-2: Dropped THIS_MODULE as Tobias suggests
* PATCH-2: Use dma_coerce_mask_and_coherent() in fsl_asrc_dma_pcm_new()
* PATCH-2: Added substream check in fsl_asrc_dma_pcm_new()
v2:
* PATCH-1: Dropped the change to SDMA driver.
* PATCH-2: Dropped useless member in private data structures.
* PATCH-2: Refined some comments in fsl_asrc_dma.c driver.
* PATCH-2: Refined commit comments.
Nicolin Chen (2):
ARM: imx: Add the secondary request into the structure for imx-sdma
ASoC: fsl_asrc: Add ASRC ASoC CPU DAI and platform drivers
.../devicetree/bindings/sound/fsl,asrc.txt | 60 ++
include/linux/platform_data/dma-imx.h | 1 +
sound/soc/fsl/Kconfig | 9 +
sound/soc/fsl/Makefile | 2 +
sound/soc/fsl/fsl_asrc.c | 992 +++++++++++++++++++++
sound/soc/fsl/fsl_asrc.h | 461 ++++++++++
sound/soc/fsl/fsl_asrc_dma.c | 386 ++++++++
7 files changed, 1911 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/fsl,asrc.txt
create mode 100644 sound/soc/fsl/fsl_asrc.c
create mode 100644 sound/soc/fsl/fsl_asrc.h
create mode 100644 sound/soc/fsl/fsl_asrc_dma.c
--
1.8.4
^ permalink raw reply
* [PATCH v6 2/2] ASoC: fsl_asrc: Add ASRC ASoC CPU DAI and platform drivers
From: Nicolin Chen @ 2014-07-29 10:08 UTC (permalink / raw)
To: broonie
Cc: mark.rutland, devicetree, alsa-devel, b42378, timur, pawel.moll,
ijc+devicetree, tiwai, linux-kernel, rdunlap, b02247, lgirdwood,
perex, varkabhadram, robh+dt, galak, grant.likely, tklauser,
shawn.guo, linuxppc-dev
In-Reply-To: <cover.1406628283.git.nicoleotsuka@gmail.com>
The Asynchronous Sample Rate Converter (ASRC) converts the sampling rate of a
signal associated with an input clock into a signal associated with a different
output clock. The driver currently works as a Front End of DPCM with other Back
Ends DAI links such as ESAI<->CS42888 and SSI<->WM8962 and SAI. It converts the
original sample rate to a common rate supported by Back Ends for playback while
converts the common rate of Back Ends to a desired rate for capture. It has 3
pairs to support three different substreams within totally 10 channels.
Signed-off-by: Nicolin Chen <nicoleotsuka@gmail.com>
Reviewed-by: Varka Bhadram <varkabhadram@gmail.com>
---
.../devicetree/bindings/sound/fsl,asrc.txt | 60 ++
sound/soc/fsl/Kconfig | 9 +
sound/soc/fsl/Makefile | 2 +
sound/soc/fsl/fsl_asrc.c | 992 +++++++++++++++++++++
sound/soc/fsl/fsl_asrc.h | 461 ++++++++++
sound/soc/fsl/fsl_asrc_dma.c | 386 ++++++++
6 files changed, 1910 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/fsl,asrc.txt
create mode 100644 sound/soc/fsl/fsl_asrc.c
create mode 100644 sound/soc/fsl/fsl_asrc.h
create mode 100644 sound/soc/fsl/fsl_asrc_dma.c
diff --git a/Documentation/devicetree/bindings/sound/fsl,asrc.txt b/Documentation/devicetree/bindings/sound/fsl,asrc.txt
new file mode 100644
index 0000000..b93362a
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/fsl,asrc.txt
@@ -0,0 +1,60 @@
+Freescale Asynchronous Sample Rate Converter (ASRC) Controller
+
+The Asynchronous Sample Rate Converter (ASRC) converts the sampling rate of a
+signal associated with an input clock into a signal associated with a different
+output clock. The driver currently works as a Front End of DPCM with other Back
+Ends Audio controller such as ESAI, SSI and SAI. It has three pairs to support
+three substreams within totally 10 channels.
+
+Required properties:
+
+ - compatible : Contains "fsl,imx35-asrc" or "fsl,imx53-asrc".
+
+ - reg : Offset and length of the register set for the device.
+
+ - interrupts : Contains the spdif interrupt.
+
+ - dmas : Generic dma devicetree binding as described in
+ Documentation/devicetree/bindings/dma/dma.txt.
+
+ - dma-names : Contains "rxa", "rxb", "rxc", "txa", "txb" and "txc".
+
+ - clocks : Contains an entry for each entry in clock-names.
+
+ - clock-names : Contains the following entries
+ "mem" Peripheral access clock to access registers.
+ "ipg" Peripheral clock to driver module.
+ "asrck_<0-f>" Clock sources for input and output clock.
+
+ - big-endian : If this property is absent, the little endian mode
+ will be in use as default. Otherwise, the big endian
+ mode will be in use for all the device registers.
+
+ - fsl,asrc-rate : Defines a mutual sample rate used by DPCM Back Ends.
+
+ - fsl,asrc-width : Defines a mutual sample width used by DPCM Back Ends.
+
+Example:
+
+asrc: asrc@02034000 {
+ compatible = "fsl,imx53-asrc";
+ reg = <0x02034000 0x4000>;
+ interrupts = <0 50 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clks 107>, <&clks 107>, <&clks 0>,
+ <&clks 0>, <&clks 0>, <&clks 0>, <&clks 0>,
+ <&clks 0>, <&clks 0>, <&clks 0>, <&clks 0>,
+ <&clks 0>, <&clks 0>, <&clks 0>, <&clks 0>,
+ <&clks 107>, <&clks 0>, <&clks 0>;
+ clock-names = "mem", "ipg", "asrck0",
+ "asrck_1", "asrck_2", "asrck_3", "asrck_4",
+ "asrck_5", "asrck_6", "asrck_7", "asrck_8",
+ "asrck_9", "asrck_a", "asrck_b", "asrck_c",
+ "asrck_d", "asrck_e", "asrck_f";
+ dmas = <&sdma 17 23 1>, <&sdma 18 23 1>, <&sdma 19 23 1>,
+ <&sdma 20 23 1>, <&sdma 21 23 1>, <&sdma 22 23 1>;
+ dma-names = "rxa", "rxb", "rxc",
+ "txa", "txb", "txc";
+ fsl,asrc-rate = <48000>;
+ fsl,asrc-width = <16>;
+ status = "okay";
+};
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 52bbe9f..2fb8a43 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -2,6 +2,15 @@ menu "SoC Audio for Freescale CPUs"
comment "Common SoC Audio options for Freescale CPUs:"
+config SND_SOC_FSL_ASRC
+ tristate "Asynchronous Sample Rate Converter (ASRC) module support"
+ select REGMAP_MMIO
+ help
+ Say Y if you want to add Asynchronous Sample Rate Converter (ASRC)
+ support for the Freescale CPUs.
+ This option is only useful for out-of-tree drivers since
+ in-tree drivers select it automatically.
+
config SND_SOC_FSL_SAI
tristate "Synchronous Audio Interface (SAI) module support"
select REGMAP_MMIO
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index db254e3..9ff5926 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -11,6 +11,7 @@ snd-soc-p1022-rdk-objs := p1022_rdk.o
obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
# Freescale SSI/DMA/SAI/SPDIF Support
+snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o
snd-soc-fsl-sai-objs := fsl_sai.o
snd-soc-fsl-ssi-y := fsl_ssi.o
snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o
@@ -18,6 +19,7 @@ snd-soc-fsl-spdif-objs := fsl_spdif.o
snd-soc-fsl-esai-objs := fsl_esai.o
snd-soc-fsl-utils-objs := fsl_utils.o
snd-soc-fsl-dma-objs := fsl_dma.o
+obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o
obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o
obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o
obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o
diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c
new file mode 100644
index 0000000..27a4a70
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc.c
@@ -0,0 +1,992 @@
+/*
+ * Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <nicoleotsuka@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/dma-imx.h>
+#include <linux/pm_runtime.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_asrc.h"
+
+#define IDEAL_RATIO_DECIMAL_DEPTH 26
+
+#define pair_err(fmt, ...) \
+ dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+
+#define pair_dbg(fmt, ...) \
+ dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+
+/* Sample rates are aligned with that defined in pcm.h file */
+static const u8 process_option[][8][2] = {
+ /* 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */
+ {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 5512Hz */
+ {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 8kHz */
+ {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 11025Hz */
+ {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 16kHz */
+ {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 22050Hz */
+ {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, /* 32kHz */
+ {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 44.1kHz */
+ {{0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 48kHz */
+ {{1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, /* 64kHz */
+ {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 88.2kHz */
+ {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 96kHz */
+ {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 176kHz */
+ {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 192kHz */
+};
+
+/* Corresponding to process_option */
+static int supported_input_rate[] = {
+ 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200,
+ 96000, 176400, 192000,
+};
+
+static int supported_asrc_rate[] = {
+ 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000,
+};
+
+/**
+ * The following tables map the relationship between asrc_inclk/asrc_outclk in
+ * fsl_asrc.h and the registers of ASRCSR
+ */
+static unsigned char input_clk_map_imx35[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+};
+
+static unsigned char output_clk_map_imx35[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+};
+
+/* i.MX53 uses the same map for input and output */
+static unsigned char input_clk_map_imx53[] = {
+/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */
+ 0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd,
+};
+
+static unsigned char output_clk_map_imx53[] = {
+/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */
+ 0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd,
+};
+
+static unsigned char *clk_map[2];
+
+/**
+ * Request ASRC pair
+ *
+ * It assigns pair by the order of A->C->B because allocation of pair B,
+ * within range [ANCA, ANCA+ANCB-1], depends on the channels of pair A
+ * while pair A and pair C are comparatively independent.
+ */
+static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
+{
+ enum asrc_pair_index index = ASRC_INVALID_PAIR;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct device *dev = &asrc_priv->pdev->dev;
+ unsigned long lock_flags;
+ int i, ret = 0;
+
+ spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+
+ for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) {
+ if (asrc_priv->pair[i] != NULL)
+ continue;
+
+ index = i;
+
+ if (i != ASRC_PAIR_B)
+ break;
+ }
+
+ if (index == ASRC_INVALID_PAIR) {
+ dev_err(dev, "all pairs are busy now\n");
+ ret = -EBUSY;
+ } else if (asrc_priv->channel_avail < channels) {
+ dev_err(dev, "can't afford required channels: %d\n", channels);
+ ret = -EINVAL;
+ } else {
+ asrc_priv->channel_avail -= channels;
+ asrc_priv->pair[index] = pair;
+ pair->channels = channels;
+ pair->index = index;
+ }
+
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+
+ return ret;
+}
+
+/**
+ * Release ASRC pair
+ *
+ * It clears the resource from asrc_priv and releases the occupied channels.
+ */
+static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ unsigned long lock_flags;
+
+ /* Make sure the pair is disabled */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_MASK(index), 0);
+
+ spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+
+ asrc_priv->channel_avail += pair->channels;
+ asrc_priv->pair[index] = NULL;
+ pair->error = 0;
+
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+}
+
+/**
+ * Configure input and output thresholds
+ */
+static void fsl_asrc_set_watermarks(struct fsl_asrc_pair *pair, u32 in, u32 out)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+
+ regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index),
+ ASRMCRi_EXTTHRSHi_MASK |
+ ASRMCRi_INFIFO_THRESHOLD_MASK |
+ ASRMCRi_OUTFIFO_THRESHOLD_MASK,
+ ASRMCRi_EXTTHRSHi |
+ ASRMCRi_INFIFO_THRESHOLD(in) |
+ ASRMCRi_OUTFIFO_THRESHOLD(out));
+}
+
+/**
+ * Calculate the total divisor between asrck clock rate and sample rate
+ *
+ * It follows the formula clk_rate = samplerate * (2 ^ prescaler) * divider
+ */
+static u32 fsl_asrc_cal_asrck_divisor(struct fsl_asrc_pair *pair, u32 div)
+{
+ u32 ps;
+
+ /* Calculate the divisors: prescaler [2^0, 2^7], divder [1, 8] */
+ for (ps = 0; div > 8; ps++)
+ div >>= 1;
+
+ return ((div - 1) << ASRCDRi_AxCPi_WIDTH) | ps;
+}
+
+/**
+ * Calculate and set the ratio for Ideal Ratio mode only
+ *
+ * The ratio is a 32-bit fixed point value with 26 fractional bits.
+ */
+static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair,
+ int inrate, int outrate)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ unsigned long ratio;
+ int i;
+
+ if (!outrate) {
+ pair_err("output rate should not be zero\n");
+ return -EINVAL;
+ }
+
+ /* Calculate the intergal part of the ratio */
+ ratio = (inrate / outrate) << IDEAL_RATIO_DECIMAL_DEPTH;
+
+ /* ... and then the 26 depth decimal part */
+ inrate %= outrate;
+
+ for (i = 1; i <= IDEAL_RATIO_DECIMAL_DEPTH; i++) {
+ inrate <<= 1;
+
+ if (inrate < outrate)
+ continue;
+
+ ratio |= 1 << (IDEAL_RATIO_DECIMAL_DEPTH - i);
+ inrate -= outrate;
+
+ if (!inrate)
+ break;
+ }
+
+ regmap_write(asrc_priv->regmap, REG_ASRIDRL(index), ratio);
+ regmap_write(asrc_priv->regmap, REG_ASRIDRH(index), ratio >> 24);
+
+ return 0;
+}
+
+/**
+ * Configure the assigned ASRC pair
+ *
+ * It configures those ASRC registers according to a configuration instance
+ * of struct asrc_config which includes in/output sample rate, width, channel
+ * and clock settings.
+ */
+static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
+{
+ struct asrc_config *config = pair->config;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ u32 inrate = config->input_sample_rate, indiv;
+ u32 outrate = config->output_sample_rate, outdiv;
+ bool ideal = config->inclk == INCLK_NONE;
+ u32 clk_index[2], div[2];
+ int in, out, channels;
+ struct clk *clk;
+
+ if (!config) {
+ pair_err("invalid pair config\n");
+ return -EINVAL;
+ }
+
+ /* Validate channels */
+ if (config->channel_num < 1 || config->channel_num > 10) {
+ pair_err("does not support %d channels\n", config->channel_num);
+ return -EINVAL;
+ }
+
+ /* Validate output width */
+ if (config->output_word_width == ASRC_WIDTH_8_BIT) {
+ pair_err("does not support 8bit width output\n");
+ return -EINVAL;
+ }
+
+ /* Validate input and output sample rates */
+ for (in = 0; in < ARRAY_SIZE(supported_input_rate); in++)
+ if (inrate == supported_input_rate[in])
+ break;
+
+ if (in == ARRAY_SIZE(supported_input_rate)) {
+ pair_err("unsupported input sample rate: %dHz\n", inrate);
+ return -EINVAL;
+ }
+
+ for (out = 0; out < ARRAY_SIZE(supported_asrc_rate); out++)
+ if (outrate == supported_asrc_rate[out])
+ break;
+
+ if (out == ARRAY_SIZE(supported_asrc_rate)) {
+ pair_err("unsupported output sample rate: %dHz\n", outrate);
+ return -EINVAL;
+ }
+
+ /* Validate input and output clock sources */
+ clk_index[IN] = clk_map[IN][config->inclk];
+ clk_index[OUT] = clk_map[OUT][config->outclk];
+
+ /* We only have output clock for ideal ratio mode */
+ clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]];
+
+ div[IN] = clk_get_rate(clk) / inrate;
+ if (div[IN] == 0) {
+ pair_err("failed to support input sample rate %dHz by asrck_%x\n",
+ inrate, clk_index[ideal ? OUT : IN]);
+ return -EINVAL;
+ }
+
+ clk = asrc_priv->asrck_clk[clk_index[OUT]];
+
+ /* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */
+ if (ideal)
+ div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE;
+ else
+ div[OUT] = clk_get_rate(clk) / outrate;
+
+ if (div[OUT] == 0) {
+ pair_err("failed to support output sample rate %dHz by asrck_%x\n",
+ outrate, clk_index[OUT]);
+ return -EINVAL;
+ }
+
+ /* Set the channel number */
+ channels = config->channel_num;
+
+ if (asrc_priv->channel_bits < 4)
+ channels /= 2;
+
+ /* Update channels for current pair */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCNCR,
+ ASRCNCR_ANCi_MASK(index, asrc_priv->channel_bits),
+ ASRCNCR_ANCi(index, channels, asrc_priv->channel_bits));
+
+ /* Default setting: Automatic selection for processing mode */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index));
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_USRi_MASK(index), 0);
+
+ /* Set the input and output clock sources */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCSR,
+ ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index),
+ ASRCSR_AICS(index, clk_index[IN]) |
+ ASRCSR_AOCS(index, clk_index[OUT]));
+
+ /* Calculate the input clock divisors */
+ indiv = fsl_asrc_cal_asrck_divisor(pair, div[IN]);
+ outdiv = fsl_asrc_cal_asrck_divisor(pair, div[OUT]);
+
+ /* Suppose indiv and outdiv includes prescaler, so add its MASK too */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCDR(index),
+ ASRCDRi_AOCPi_MASK(index) | ASRCDRi_AICPi_MASK(index) |
+ ASRCDRi_AOCDi_MASK(index) | ASRCDRi_AICDi_MASK(index),
+ ASRCDRi_AOCP(index, outdiv) | ASRCDRi_AICP(index, indiv));
+
+ /* Implement word_width configurations */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRMCR1(index),
+ ASRMCR1i_OW16_MASK | ASRMCR1i_IWD_MASK,
+ ASRMCR1i_OW16(config->output_word_width) |
+ ASRMCR1i_IWD(config->input_word_width));
+
+ /* Enable BUFFER STALL */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index),
+ ASRMCRi_BUFSTALLi_MASK, ASRMCRi_BUFSTALLi);
+
+ /* Set default thresholds for input and output FIFO */
+ fsl_asrc_set_watermarks(pair, ASRC_INPUTFIFO_THRESHOLD,
+ ASRC_INPUTFIFO_THRESHOLD);
+
+ /* Configure the followings only for Ideal Ratio mode */
+ if (!ideal)
+ return 0;
+
+ /* Clear ASTSx bit to use Ideal Ratio mode */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ATSi_MASK(index), 0);
+
+ /* Enable Ideal Ratio mode */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_IDRi_MASK(index) | ASRCTR_USRi_MASK(index),
+ ASRCTR_IDR(index) | ASRCTR_USR(index));
+
+ /* Apply configurations for pre- and post-processing */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCFG,
+ ASRCFG_PREMODi_MASK(index) | ASRCFG_POSTMODi_MASK(index),
+ ASRCFG_PREMOD(index, process_option[in][out][0]) |
+ ASRCFG_POSTMOD(index, process_option[in][out][1]));
+
+ return fsl_asrc_set_ideal_ratio(pair, inrate, outrate);
+}
+
+/**
+ * Start the assigned ASRC pair
+ *
+ * It enables the assigned pair and makes it stopped at the stall level.
+ */
+static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ int reg, retry = 10, i;
+
+ /* Enable the current pair */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_MASK(index), ASRCTR_ASRCE(index));
+
+ /* Wait for status of initialization */
+ do {
+ udelay(5);
+ regmap_read(asrc_priv->regmap, REG_ASRCFG, ®);
+ reg &= ASRCFG_INIRQi_MASK(index);
+ } while (!reg && --retry);
+
+ /* Make the input fifo to ASRC STALL level */
+ regmap_read(asrc_priv->regmap, REG_ASRCNCR, ®);
+ for (i = 0; i < pair->channels * 4; i++)
+ regmap_write(asrc_priv->regmap, REG_ASRDI(index), 0);
+
+ /* Enable overload interrupt */
+ regmap_write(asrc_priv->regmap, REG_ASRIER, ASRIER_AOLIE);
+}
+
+/**
+ * Stop the assigned ASRC pair
+ */
+static void fsl_asrc_stop_pair(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+
+ /* Stop the current pair */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_MASK(index), 0);
+}
+
+/**
+ * Get DMA channel according to the pair and direction.
+ */
+struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ char name[4];
+
+ sprintf(name, "%cx%c", dir == IN ? 'r' : 't', index + 'a');
+
+ return dma_request_slave_channel(&asrc_priv->pdev->dev, name);
+}
+EXPORT_SYMBOL_GPL(fsl_asrc_get_dma_channel);
+
+static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai);
+ int width = snd_pcm_format_width(params_format(params));
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ unsigned int channels = params_channels(params);
+ unsigned int rate = params_rate(params);
+ struct asrc_config config;
+ int word_width, ret;
+
+ ret = fsl_asrc_request_pair(channels, pair);
+ if (ret) {
+ dev_err(dai->dev, "fail to request asrc pair\n");
+ return ret;
+ }
+
+ pair->config = &config;
+
+ if (width == 16)
+ width = ASRC_WIDTH_16_BIT;
+ else
+ width = ASRC_WIDTH_24_BIT;
+
+ if (asrc_priv->asrc_width == 16)
+ word_width = ASRC_WIDTH_16_BIT;
+ else
+ word_width = ASRC_WIDTH_24_BIT;
+
+ config.pair = pair->index;
+ config.channel_num = channels;
+ config.inclk = INCLK_NONE;
+ config.outclk = OUTCLK_ASRCK1_CLK;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ config.input_word_width = width;
+ config.output_word_width = word_width;
+ config.input_sample_rate = rate;
+ config.output_sample_rate = asrc_priv->asrc_rate;
+ } else {
+ config.input_word_width = word_width;
+ config.output_word_width = width;
+ config.input_sample_rate = asrc_priv->asrc_rate;
+ config.output_sample_rate = rate;
+ }
+
+ ret = fsl_asrc_config_pair(pair);
+ if (ret) {
+ dev_err(dai->dev, "fail to config asrc pair\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+
+ if (pair)
+ fsl_asrc_release_pair(pair);
+
+ return 0;
+}
+
+static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ fsl_asrc_start_pair(pair);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ fsl_asrc_stop_pair(pair);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops fsl_asrc_dai_ops = {
+ .hw_params = fsl_asrc_dai_hw_params,
+ .hw_free = fsl_asrc_dai_hw_free,
+ .trigger = fsl_asrc_dai_trigger,
+};
+
+static int fsl_asrc_dai_probe(struct snd_soc_dai *dai)
+{
+ struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai, &asrc_priv->dma_params_tx,
+ &asrc_priv->dma_params_rx);
+
+ return 0;
+}
+
+#define FSL_ASRC_RATES SNDRV_PCM_RATE_8000_192000
+#define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FORMAT_S20_3LE)
+
+static struct snd_soc_dai_driver fsl_asrc_dai = {
+ .probe = fsl_asrc_dai_probe,
+ .playback = {
+ .stream_name = "ASRC-Playback",
+ .channels_min = 1,
+ .channels_max = 10,
+ .rates = FSL_ASRC_RATES,
+ .formats = FSL_ASRC_FORMATS,
+ },
+ .capture = {
+ .stream_name = "ASRC-Capture",
+ .channels_min = 1,
+ .channels_max = 10,
+ .rates = FSL_ASRC_RATES,
+ .formats = FSL_ASRC_FORMATS,
+ },
+ .ops = &fsl_asrc_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_asrc_component = {
+ .name = "fsl-asrc-dai",
+};
+
+static bool fsl_asrc_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_ASRCTR:
+ case REG_ASRIER:
+ case REG_ASRCNCR:
+ case REG_ASRCFG:
+ case REG_ASRCSR:
+ case REG_ASRCDR1:
+ case REG_ASRCDR2:
+ case REG_ASRSTR:
+ case REG_ASRPM1:
+ case REG_ASRPM2:
+ case REG_ASRPM3:
+ case REG_ASRPM4:
+ case REG_ASRPM5:
+ case REG_ASRTFR1:
+ case REG_ASRCCR:
+ case REG_ASRDOA:
+ case REG_ASRDOB:
+ case REG_ASRDOC:
+ case REG_ASRIDRHA:
+ case REG_ASRIDRLA:
+ case REG_ASRIDRHB:
+ case REG_ASRIDRLB:
+ case REG_ASRIDRHC:
+ case REG_ASRIDRLC:
+ case REG_ASR76K:
+ case REG_ASR56K:
+ case REG_ASRMCRA:
+ case REG_ASRFSTA:
+ case REG_ASRMCRB:
+ case REG_ASRFSTB:
+ case REG_ASRMCRC:
+ case REG_ASRFSTC:
+ case REG_ASRMCR1A:
+ case REG_ASRMCR1B:
+ case REG_ASRMCR1C:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_asrc_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_ASRSTR:
+ case REG_ASRDIA:
+ case REG_ASRDIB:
+ case REG_ASRDIC:
+ case REG_ASRDOA:
+ case REG_ASRDOB:
+ case REG_ASRDOC:
+ case REG_ASRFSTA:
+ case REG_ASRFSTB:
+ case REG_ASRFSTC:
+ case REG_ASRCFG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_asrc_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_ASRCTR:
+ case REG_ASRIER:
+ case REG_ASRCNCR:
+ case REG_ASRCFG:
+ case REG_ASRCSR:
+ case REG_ASRCDR1:
+ case REG_ASRCDR2:
+ case REG_ASRSTR:
+ case REG_ASRPM1:
+ case REG_ASRPM2:
+ case REG_ASRPM3:
+ case REG_ASRPM4:
+ case REG_ASRPM5:
+ case REG_ASRTFR1:
+ case REG_ASRCCR:
+ case REG_ASRDIA:
+ case REG_ASRDIB:
+ case REG_ASRDIC:
+ case REG_ASRIDRHA:
+ case REG_ASRIDRLA:
+ case REG_ASRIDRHB:
+ case REG_ASRIDRLB:
+ case REG_ASRIDRHC:
+ case REG_ASRIDRLC:
+ case REG_ASR76K:
+ case REG_ASR56K:
+ case REG_ASRMCRA:
+ case REG_ASRMCRB:
+ case REG_ASRMCRC:
+ case REG_ASRMCR1A:
+ case REG_ASRMCR1B:
+ case REG_ASRMCR1C:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static struct regmap_config fsl_asrc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .max_register = REG_ASRMCR1C,
+ .readable_reg = fsl_asrc_readable_reg,
+ .volatile_reg = fsl_asrc_volatile_reg,
+ .writeable_reg = fsl_asrc_writeable_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+/**
+ * Initialize ASRC registers with a default configurations
+ */
+static int fsl_asrc_init(struct fsl_asrc *asrc_priv)
+{
+ /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */
+ regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN);
+
+ /* Disable interrupt by default */
+ regmap_write(asrc_priv->regmap, REG_ASRIER, 0x0);
+
+ /* Apply recommended settings for parameters from Reference Manual */
+ regmap_write(asrc_priv->regmap, REG_ASRPM1, 0x7fffff);
+ regmap_write(asrc_priv->regmap, REG_ASRPM2, 0x255555);
+ regmap_write(asrc_priv->regmap, REG_ASRPM3, 0xff7280);
+ regmap_write(asrc_priv->regmap, REG_ASRPM4, 0xff7280);
+ regmap_write(asrc_priv->regmap, REG_ASRPM5, 0xff7280);
+
+ /* Base address for task queue FIFO. Set to 0x7C */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1,
+ ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc));
+
+ /* Set the processing clock for 76KHz to 133M */
+ regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6);
+
+ /* Set the processing clock for 56KHz to 133M */
+ return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947);
+}
+
+/**
+ * Interrupt handler for ASRC
+ */
+static irqreturn_t fsl_asrc_isr(int irq, void *dev_id)
+{
+ struct fsl_asrc *asrc_priv = (struct fsl_asrc *)dev_id;
+ struct device *dev = &asrc_priv->pdev->dev;
+ enum asrc_pair_index index;
+ u32 status;
+
+ regmap_read(asrc_priv->regmap, REG_ASRSTR, &status);
+
+ /* Clean overload error */
+ regmap_write(asrc_priv->regmap, REG_ASRSTR, ASRSTR_AOLE);
+
+ /*
+ * We here use dev_dbg() for all exceptions because ASRC itself does
+ * not care if FIFO overflowed or underrun while a warning in the
+ * interrupt would result a ridged conversion.
+ */
+ for (index = ASRC_PAIR_A; index < ASRC_PAIR_MAX_NUM; index++) {
+ if (!asrc_priv->pair[index])
+ continue;
+
+ if (status & ASRSTR_ATQOL) {
+ asrc_priv->pair[index]->error |= ASRC_TASK_Q_OVERLOAD;
+ dev_dbg(dev, "ASRC Task Queue FIFO overload\n");
+ }
+
+ if (status & ASRSTR_AOOL(index)) {
+ asrc_priv->pair[index]->error |= ASRC_OUTPUT_TASK_OVERLOAD;
+ pair_dbg("Output Task Overload\n");
+ }
+
+ if (status & ASRSTR_AIOL(index)) {
+ asrc_priv->pair[index]->error |= ASRC_INPUT_TASK_OVERLOAD;
+ pair_dbg("Input Task Overload\n");
+ }
+
+ if (status & ASRSTR_AODO(index)) {
+ asrc_priv->pair[index]->error |= ASRC_OUTPUT_BUFFER_OVERFLOW;
+ pair_dbg("Output Data Buffer has overflowed\n");
+ }
+
+ if (status & ASRSTR_AIDU(index)) {
+ asrc_priv->pair[index]->error |= ASRC_INPUT_BUFFER_UNDERRUN;
+ pair_dbg("Input Data Buffer has underflowed\n");
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int fsl_asrc_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_asrc *asrc_priv;
+ struct resource *res;
+ void __iomem *regs;
+ int irq, ret, i;
+ char tmp[16];
+
+ asrc_priv = devm_kzalloc(&pdev->dev, sizeof(*asrc_priv), GFP_KERNEL);
+ if (!asrc_priv)
+ return -ENOMEM;
+
+ asrc_priv->pdev = pdev;
+ strcpy(asrc_priv->name, np->name);
+
+ /* Get the addresses and IRQ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ asrc_priv->paddr = res->start;
+
+ /* Register regmap and let it prepare core clock */
+ if (of_property_read_bool(np, "big-endian"))
+ fsl_asrc_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG;
+
+ asrc_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs,
+ &fsl_asrc_regmap_config);
+ if (IS_ERR(asrc_priv->regmap)) {
+ dev_err(&pdev->dev, "failed to init regmap\n");
+ return PTR_ERR(asrc_priv->regmap);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", np->full_name);
+ return irq;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, fsl_asrc_isr, 0,
+ asrc_priv->name, asrc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret);
+ return ret;
+ }
+
+ asrc_priv->mem_clk = devm_clk_get(&pdev->dev, "mem");
+ if (IS_ERR(asrc_priv->mem_clk)) {
+ dev_err(&pdev->dev, "failed to get mem clock\n");
+ return PTR_ERR(asrc_priv->ipg_clk);
+ }
+
+ asrc_priv->ipg_clk = devm_clk_get(&pdev->dev, "ipg");
+ if (IS_ERR(asrc_priv->ipg_clk)) {
+ dev_err(&pdev->dev, "failed to get ipg clock\n");
+ return PTR_ERR(asrc_priv->ipg_clk);
+ }
+
+ for (i = 0; i < ASRC_CLK_MAX_NUM; i++) {
+ sprintf(tmp, "asrck_%x", i);
+ asrc_priv->asrck_clk[i] = devm_clk_get(&pdev->dev, tmp);
+ if (IS_ERR(asrc_priv->asrck_clk[i])) {
+ dev_err(&pdev->dev, "failed to get %s clock\n", tmp);
+ return PTR_ERR(asrc_priv->asrck_clk[i]);
+ }
+ }
+
+ if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx35-asrc")) {
+ asrc_priv->channel_bits = 3;
+ clk_map[IN] = input_clk_map_imx35;
+ clk_map[OUT] = output_clk_map_imx35;
+ } else {
+ asrc_priv->channel_bits = 4;
+ clk_map[IN] = input_clk_map_imx53;
+ clk_map[OUT] = output_clk_map_imx53;
+ }
+
+ ret = fsl_asrc_init(asrc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init asrc %d\n", ret);
+ return -EINVAL;
+ }
+
+ asrc_priv->channel_avail = 10;
+
+ ret = of_property_read_u32(np, "fsl,asrc-rate",
+ &asrc_priv->asrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(np, "fsl,asrc-width",
+ &asrc_priv->asrc_width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output width\n");
+ return -EINVAL;
+ }
+
+ if (asrc_priv->asrc_width != 16 && asrc_priv->asrc_width != 24) {
+ dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n");
+ asrc_priv->asrc_width = 24;
+ }
+
+ platform_set_drvdata(pdev, asrc_priv);
+ pm_runtime_enable(&pdev->dev);
+ spin_lock_init(&asrc_priv->lock);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component,
+ &fsl_asrc_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ASoC DAI\n");
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_platform(&pdev->dev, &fsl_asrc_platform);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ASoC platform\n");
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "driver registered\n");
+
+ return 0;
+}
+
+#if CONFIG_PM_RUNTIME
+static int fsl_asrc_runtime_resume(struct device *dev)
+{
+ struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+ int i;
+
+ clk_prepare_enable(asrc_priv->mem_clk);
+ clk_prepare_enable(asrc_priv->ipg_clk);
+ for (i = 0; i < ASRC_CLK_MAX_NUM; i++)
+ clk_prepare_enable(asrc_priv->asrck_clk[i]);
+
+ return 0;
+}
+
+static int fsl_asrc_runtime_suspend(struct device *dev)
+{
+ struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < ASRC_CLK_MAX_NUM; i++)
+ clk_disable_unprepare(asrc_priv->asrck_clk[i]);
+ clk_disable_unprepare(asrc_priv->ipg_clk);
+ clk_disable_unprepare(asrc_priv->mem_clk);
+
+ return 0;
+}
+#endif /* CONFIG_PM_RUNTIME */
+
+#if CONFIG_PM_SLEEP
+static int fsl_asrc_suspend(struct device *dev)
+{
+ struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+
+ regcache_cache_only(asrc_priv->regmap, true);
+ regcache_mark_dirty(asrc_priv->regmap);
+
+ return 0;
+}
+
+static int fsl_asrc_resume(struct device *dev)
+{
+ struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+ u32 asrctr;
+
+ /* Stop all pairs provisionally */
+ regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr);
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_ALL_MASK, 0);
+
+ /* Restore all registers */
+ regcache_cache_only(asrc_priv->regmap, false);
+ regcache_sync(asrc_priv->regmap);
+
+ /* Restart enabled pairs */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_ALL_MASK, asrctr);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_asrc_pm = {
+ SET_RUNTIME_PM_OPS(fsl_asrc_runtime_suspend, fsl_asrc_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_asrc_suspend, fsl_asrc_resume)
+};
+
+static const struct of_device_id fsl_asrc_ids[] = {
+ { .compatible = "fsl,imx35-asrc", },
+ { .compatible = "fsl,imx53-asrc", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_asrc_ids);
+
+static struct platform_driver fsl_asrc_driver = {
+ .probe = fsl_asrc_probe,
+ .driver = {
+ .name = "fsl-asrc",
+ .of_match_table = fsl_asrc_ids,
+ .pm = &fsl_asrc_pm,
+ },
+};
+module_platform_driver(fsl_asrc_driver);
+
+MODULE_DESCRIPTION("Freescale ASRC ASoC driver");
+MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>");
+MODULE_ALIAS("platform:fsl-asrc");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h
new file mode 100644
index 0000000..a3f211f
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc.h
@@ -0,0 +1,461 @@
+/*
+ * fsl_asrc.h - Freescale ASRC ALSA SoC header file
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <nicoleotsuka@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef _FSL_ASRC_H
+#define _FSL_ASRC_H
+
+#define IN 0
+#define OUT 1
+
+#define ASRC_DMA_BUFFER_NUM 2
+#define ASRC_INPUTFIFO_THRESHOLD 32
+#define ASRC_OUTPUTFIFO_THRESHOLD 32
+#define ASRC_FIFO_THRESHOLD_MIN 0
+#define ASRC_FIFO_THRESHOLD_MAX 63
+#define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4)
+#define ASRC_MAX_BUFFER_SIZE (1024 * 48)
+#define ASRC_OUTPUT_LAST_SAMPLE 8
+
+#define IDEAL_RATIO_RATE 1000000
+
+#define REG_ASRCTR 0x00
+#define REG_ASRIER 0x04
+#define REG_ASRCNCR 0x0C
+#define REG_ASRCFG 0x10
+#define REG_ASRCSR 0x14
+
+#define REG_ASRCDR1 0x18
+#define REG_ASRCDR2 0x1C
+#define REG_ASRCDR(i) ((i < 2) ? REG_ASRCDR1 : REG_ASRCDR2)
+
+#define REG_ASRSTR 0x20
+#define REG_ASRRA 0x24
+#define REG_ASRRB 0x28
+#define REG_ASRRC 0x2C
+#define REG_ASRPM1 0x40
+#define REG_ASRPM2 0x44
+#define REG_ASRPM3 0x48
+#define REG_ASRPM4 0x4C
+#define REG_ASRPM5 0x50
+#define REG_ASRTFR1 0x54
+#define REG_ASRCCR 0x5C
+
+#define REG_ASRDIA 0x60
+#define REG_ASRDOA 0x64
+#define REG_ASRDIB 0x68
+#define REG_ASRDOB 0x6C
+#define REG_ASRDIC 0x70
+#define REG_ASRDOC 0x74
+#define REG_ASRDI(i) (REG_ASRDIA + (i << 3))
+#define REG_ASRDO(i) (REG_ASRDOA + (i << 3))
+#define REG_ASRDx(x, i) (x == IN ? REG_ASRDI(i) : REG_ASRDO(i))
+
+#define REG_ASRIDRHA 0x80
+#define REG_ASRIDRLA 0x84
+#define REG_ASRIDRHB 0x88
+#define REG_ASRIDRLB 0x8C
+#define REG_ASRIDRHC 0x90
+#define REG_ASRIDRLC 0x94
+#define REG_ASRIDRH(i) (REG_ASRIDRHA + (i << 3))
+#define REG_ASRIDRL(i) (REG_ASRIDRLA + (i << 3))
+
+#define REG_ASR76K 0x98
+#define REG_ASR56K 0x9C
+
+#define REG_ASRMCRA 0xA0
+#define REG_ASRFSTA 0xA4
+#define REG_ASRMCRB 0xA8
+#define REG_ASRFSTB 0xAC
+#define REG_ASRMCRC 0xB0
+#define REG_ASRFSTC 0xB4
+#define REG_ASRMCR(i) (REG_ASRMCRA + (i << 3))
+#define REG_ASRFST(i) (REG_ASRFSTA + (i << 3))
+
+#define REG_ASRMCR1A 0xC0
+#define REG_ASRMCR1B 0xC4
+#define REG_ASRMCR1C 0xC8
+#define REG_ASRMCR1(i) (REG_ASRMCR1A + (i << 2))
+
+
+/* REG0 0x00 REG_ASRCTR */
+#define ASRCTR_ATSi_SHIFT(i) (20 + i)
+#define ASRCTR_ATSi_MASK(i) (1 << ASRCTR_ATSi_SHIFT(i))
+#define ASRCTR_ATS(i) (1 << ASRCTR_ATSi_SHIFT(i))
+#define ASRCTR_USRi_SHIFT(i) (14 + (i << 1))
+#define ASRCTR_USRi_MASK(i) (1 << ASRCTR_USRi_SHIFT(i))
+#define ASRCTR_USR(i) (1 << ASRCTR_USRi_SHIFT(i))
+#define ASRCTR_IDRi_SHIFT(i) (13 + (i << 1))
+#define ASRCTR_IDRi_MASK(i) (1 << ASRCTR_IDRi_SHIFT(i))
+#define ASRCTR_IDR(i) (1 << ASRCTR_IDRi_SHIFT(i))
+#define ASRCTR_SRST_SHIFT 4
+#define ASRCTR_SRST_MASK (1 << ASRCTR_SRST_SHIFT)
+#define ASRCTR_SRST (1 << ASRCTR_SRST_SHIFT)
+#define ASRCTR_ASRCEi_SHIFT(i) (1 + i)
+#define ASRCTR_ASRCEi_MASK(i) (1 << ASRCTR_ASRCEi_SHIFT(i))
+#define ASRCTR_ASRCE(i) (1 << ASRCTR_ASRCEi_SHIFT(i))
+#define ASRCTR_ASRCEi_ALL_MASK (0x7 << ASRCTR_ASRCEi_SHIFT(0))
+#define ASRCTR_ASRCEN_SHIFT 0
+#define ASRCTR_ASRCEN_MASK (1 << ASRCTR_ASRCEN_SHIFT)
+#define ASRCTR_ASRCEN (1 << ASRCTR_ASRCEN_SHIFT)
+
+/* REG1 0x04 REG_ASRIER */
+#define ASRIER_AFPWE_SHIFT 7
+#define ASRIER_AFPWE_MASK (1 << ASRIER_AFPWE_SHIFT)
+#define ASRIER_AFPWE (1 << ASRIER_AFPWE_SHIFT)
+#define ASRIER_AOLIE_SHIFT 6
+#define ASRIER_AOLIE_MASK (1 << ASRIER_AOLIE_SHIFT)
+#define ASRIER_AOLIE (1 << ASRIER_AOLIE_SHIFT)
+#define ASRIER_ADOEi_SHIFT(i) (3 + i)
+#define ASRIER_ADOEi_MASK(i) (1 << ASRIER_ADOEi_SHIFT(i))
+#define ASRIER_ADOE(i) (1 << ASRIER_ADOEi_SHIFT(i))
+#define ASRIER_ADIEi_SHIFT(i) (0 + i)
+#define ASRIER_ADIEi_MASK(i) (1 << ASRIER_ADIEi_SHIFT(i))
+#define ASRIER_ADIE(i) (1 << ASRIER_ADIEi_SHIFT(i))
+
+/* REG2 0x0C REG_ASRCNCR */
+#define ASRCNCR_ANCi_SHIFT(i, b) (b * i)
+#define ASRCNCR_ANCi_MASK(i, b) (((1 << b) - 1) << ASRCNCR_ANCi_SHIFT(i, b))
+#define ASRCNCR_ANCi(i, v, b) ((v << ASRCNCR_ANCi_SHIFT(i, b)) & ASRCNCR_ANCi_MASK(i, b))
+
+/* REG3 0x10 REG_ASRCFG */
+#define ASRCFG_INIRQi_SHIFT(i) (21 + i)
+#define ASRCFG_INIRQi_MASK(i) (1 << ASRCFG_INIRQi_SHIFT(i))
+#define ASRCFG_INIRQi (1 << ASRCFG_INIRQi_SHIFT(i))
+#define ASRCFG_NDPRi_SHIFT(i) (18 + i)
+#define ASRCFG_NDPRi_MASK(i) (1 << ASRCFG_NDPRi_SHIFT(i))
+#define ASRCFG_NDPRi (1 << ASRCFG_NDPRi_SHIFT(i))
+#define ASRCFG_POSTMODi_SHIFT(i) (8 + (i << 2))
+#define ASRCFG_POSTMODi_WIDTH 2
+#define ASRCFG_POSTMODi_MASK(i) (((1 << ASRCFG_POSTMODi_WIDTH) - 1) << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_POSTMOD(i, v) ((v) << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_POSTMODi_UP(i) (0 << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_POSTMODi_DCON(i) (1 << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_POSTMODi_DOWN(i) (2 << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_PREMODi_SHIFT(i) (6 + (i << 2))
+#define ASRCFG_PREMODi_WIDTH 2
+#define ASRCFG_PREMODi_MASK(i) (((1 << ASRCFG_PREMODi_WIDTH) - 1) << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMOD(i, v) ((v) << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMODi_UP(i) (0 << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMODi_DCON(i) (1 << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMODi_DOWN(i) (2 << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMODi_BYPASS(i) (3 << ASRCFG_PREMODi_SHIFT(i))
+
+/* REG4 0x14 REG_ASRCSR */
+#define ASRCSR_AxCSi_WIDTH 4
+#define ASRCSR_AxCSi_MASK ((1 << ASRCSR_AxCSi_WIDTH) - 1)
+#define ASRCSR_AOCSi_SHIFT(i) (12 + (i << 2))
+#define ASRCSR_AOCSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AOCSi_SHIFT(i))
+#define ASRCSR_AOCS(i, v) ((v) << ASRCSR_AOCSi_SHIFT(i))
+#define ASRCSR_AICSi_SHIFT(i) (i << 2)
+#define ASRCSR_AICSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AICSi_SHIFT(i))
+#define ASRCSR_AICS(i, v) ((v) << ASRCSR_AICSi_SHIFT(i))
+
+/* REG5&6 0x18 & 0x1C REG_ASRCDR1 & ASRCDR2 */
+#define ASRCDRi_AxCPi_WIDTH 3
+#define ASRCDRi_AICPi_SHIFT(i) (0 + (i % 2) * 6)
+#define ASRCDRi_AICPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICPi_SHIFT(i))
+#define ASRCDRi_AICP(i, v) ((v) << ASRCDRi_AICPi_SHIFT(i))
+#define ASRCDRi_AICDi_SHIFT(i) (3 + (i % 2) * 6)
+#define ASRCDRi_AICDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICDi_SHIFT(i))
+#define ASRCDRi_AICD(i, v) ((v) << ASRCDRi_AICDi_SHIFT(i))
+#define ASRCDRi_AOCPi_SHIFT(i) ((i < 2) ? 12 + i * 6 : 6)
+#define ASRCDRi_AOCPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCPi_SHIFT(i))
+#define ASRCDRi_AOCP(i, v) ((v) << ASRCDRi_AOCPi_SHIFT(i))
+#define ASRCDRi_AOCDi_SHIFT(i) ((i < 2) ? 15 + i * 6 : 9)
+#define ASRCDRi_AOCDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCDi_SHIFT(i))
+#define ASRCDRi_AOCD(i, v) ((v) << ASRCDRi_AOCDi_SHIFT(i))
+
+/* REG7 0x20 REG_ASRSTR */
+#define ASRSTR_DSLCNT_SHIFT 21
+#define ASRSTR_DSLCNT_MASK (1 << ASRSTR_DSLCNT_SHIFT)
+#define ASRSTR_DSLCNT (1 << ASRSTR_DSLCNT_SHIFT)
+#define ASRSTR_ATQOL_SHIFT 20
+#define ASRSTR_ATQOL_MASK (1 << ASRSTR_ATQOL_SHIFT)
+#define ASRSTR_ATQOL (1 << ASRSTR_ATQOL_SHIFT)
+#define ASRSTR_AOOLi_SHIFT(i) (17 + i)
+#define ASRSTR_AOOLi_MASK(i) (1 << ASRSTR_AOOLi_SHIFT(i))
+#define ASRSTR_AOOL(i) (1 << ASRSTR_AOOLi_SHIFT(i))
+#define ASRSTR_AIOLi_SHIFT(i) (14 + i)
+#define ASRSTR_AIOLi_MASK(i) (1 << ASRSTR_AIOLi_SHIFT(i))
+#define ASRSTR_AIOL(i) (1 << ASRSTR_AIOLi_SHIFT(i))
+#define ASRSTR_AODOi_SHIFT(i) (11 + i)
+#define ASRSTR_AODOi_MASK(i) (1 << ASRSTR_AODOi_SHIFT(i))
+#define ASRSTR_AODO(i) (1 << ASRSTR_AODOi_SHIFT(i))
+#define ASRSTR_AIDUi_SHIFT(i) (8 + i)
+#define ASRSTR_AIDUi_MASK(i) (1 << ASRSTR_AIDUi_SHIFT(i))
+#define ASRSTR_AIDU(i) (1 << ASRSTR_AIDUi_SHIFT(i))
+#define ASRSTR_FPWT_SHIFT 7
+#define ASRSTR_FPWT_MASK (1 << ASRSTR_FPWT_SHIFT)
+#define ASRSTR_FPWT (1 << ASRSTR_FPWT_SHIFT)
+#define ASRSTR_AOLE_SHIFT 6
+#define ASRSTR_AOLE_MASK (1 << ASRSTR_AOLE_SHIFT)
+#define ASRSTR_AOLE (1 << ASRSTR_AOLE_SHIFT)
+#define ASRSTR_AODEi_SHIFT(i) (3 + i)
+#define ASRSTR_AODFi_MASK(i) (1 << ASRSTR_AODEi_SHIFT(i))
+#define ASRSTR_AODF(i) (1 << ASRSTR_AODEi_SHIFT(i))
+#define ASRSTR_AIDEi_SHIFT(i) (0 + i)
+#define ASRSTR_AIDEi_MASK(i) (1 << ASRSTR_AIDEi_SHIFT(i))
+#define ASRSTR_AIDE(i) (1 << ASRSTR_AIDEi_SHIFT(i))
+
+/* REG10 0x54 REG_ASRTFR1 */
+#define ASRTFR1_TF_BASE_WIDTH 7
+#define ASRTFR1_TF_BASE_SHIFT 6
+#define ASRTFR1_TF_BASE_MASK (((1 << ASRTFR1_TF_BASE_WIDTH) - 1) << ASRTFR1_TF_BASE_SHIFT)
+#define ASRTFR1_TF_BASE(i) ((i) << ASRTFR1_TF_BASE_SHIFT)
+
+/*
+ * REG22 0xA0 REG_ASRMCRA
+ * REG24 0xA8 REG_ASRMCRB
+ * REG26 0xB0 REG_ASRMCRC
+ */
+#define ASRMCRi_ZEROBUFi_SHIFT 23
+#define ASRMCRi_ZEROBUFi_MASK (1 << ASRMCRi_ZEROBUFi_SHIFT)
+#define ASRMCRi_ZEROBUFi (1 << ASRMCRi_ZEROBUFi_SHIFT)
+#define ASRMCRi_EXTTHRSHi_SHIFT 22
+#define ASRMCRi_EXTTHRSHi_MASK (1 << ASRMCRi_EXTTHRSHi_SHIFT)
+#define ASRMCRi_EXTTHRSHi (1 << ASRMCRi_EXTTHRSHi_SHIFT)
+#define ASRMCRi_BUFSTALLi_SHIFT 21
+#define ASRMCRi_BUFSTALLi_MASK (1 << ASRMCRi_BUFSTALLi_SHIFT)
+#define ASRMCRi_BUFSTALLi (1 << ASRMCRi_BUFSTALLi_SHIFT)
+#define ASRMCRi_BYPASSPOLYi_SHIFT 20
+#define ASRMCRi_BYPASSPOLYi_MASK (1 << ASRMCRi_BYPASSPOLYi_SHIFT)
+#define ASRMCRi_BYPASSPOLYi (1 << ASRMCRi_BYPASSPOLYi_SHIFT)
+#define ASRMCRi_OUTFIFO_THRESHOLD_WIDTH 6
+#define ASRMCRi_OUTFIFO_THRESHOLD_SHIFT 12
+#define ASRMCRi_OUTFIFO_THRESHOLD_MASK (((1 << ASRMCRi_OUTFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT)
+#define ASRMCRi_OUTFIFO_THRESHOLD(v) (((v) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT) & ASRMCRi_OUTFIFO_THRESHOLD_MASK)
+#define ASRMCRi_RSYNIFi_SHIFT 11
+#define ASRMCRi_RSYNIFi_MASK (1 << ASRMCRi_RSYNIFi_SHIFT)
+#define ASRMCRi_RSYNIFi (1 << ASRMCRi_RSYNIFi_SHIFT)
+#define ASRMCRi_RSYNOFi_SHIFT 10
+#define ASRMCRi_RSYNOFi_MASK (1 << ASRMCRi_RSYNOFi_SHIFT)
+#define ASRMCRi_RSYNOFi (1 << ASRMCRi_RSYNOFi_SHIFT)
+#define ASRMCRi_INFIFO_THRESHOLD_WIDTH 6
+#define ASRMCRi_INFIFO_THRESHOLD_SHIFT 0
+#define ASRMCRi_INFIFO_THRESHOLD_MASK (((1 << ASRMCRi_INFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_INFIFO_THRESHOLD_SHIFT)
+#define ASRMCRi_INFIFO_THRESHOLD(v) (((v) << ASRMCRi_INFIFO_THRESHOLD_SHIFT) & ASRMCRi_INFIFO_THRESHOLD_MASK)
+
+/*
+ * REG23 0xA4 REG_ASRFSTA
+ * REG25 0xAC REG_ASRFSTB
+ * REG27 0xB4 REG_ASRFSTC
+ */
+#define ASRFSTi_OAFi_SHIFT 23
+#define ASRFSTi_OAFi_MASK (1 << ASRFSTi_OAFi_SHIFT)
+#define ASRFSTi_OAFi (1 << ASRFSTi_OAFi_SHIFT)
+#define ASRFSTi_OUTPUT_FIFO_WIDTH 7
+#define ASRFSTi_OUTPUT_FIFO_SHIFT 12
+#define ASRFSTi_OUTPUT_FIFO_MASK (((1 << ASRFSTi_OUTPUT_FIFO_WIDTH) - 1) << ASRFSTi_OUTPUT_FIFO_SHIFT)
+#define ASRFSTi_IAEi_SHIFT 11
+#define ASRFSTi_IAEi_MASK (1 << ASRFSTi_OAFi_SHIFT)
+#define ASRFSTi_IAEi (1 << ASRFSTi_OAFi_SHIFT)
+#define ASRFSTi_INPUT_FIFO_WIDTH 7
+#define ASRFSTi_INPUT_FIFO_SHIFT 0
+#define ASRFSTi_INPUT_FIFO_MASK ((1 << ASRFSTi_INPUT_FIFO_WIDTH) - 1)
+
+/* REG28 0xC0 & 0xC4 & 0xC8 REG_ASRMCR1i */
+#define ASRMCR1i_IWD_WIDTH 3
+#define ASRMCR1i_IWD_SHIFT 9
+#define ASRMCR1i_IWD_MASK (((1 << ASRMCR1i_IWD_WIDTH) - 1) << ASRMCR1i_IWD_SHIFT)
+#define ASRMCR1i_IWD(v) ((v) << ASRMCR1i_IWD_SHIFT)
+#define ASRMCR1i_IMSB_SHIFT 8
+#define ASRMCR1i_IMSB_MASK (1 << ASRMCR1i_IMSB_SHIFT)
+#define ASRMCR1i_IMSB_MSB (1 << ASRMCR1i_IMSB_SHIFT)
+#define ASRMCR1i_IMSB_LSB (0 << ASRMCR1i_IMSB_SHIFT)
+#define ASRMCR1i_OMSB_SHIFT 2
+#define ASRMCR1i_OMSB_MASK (1 << ASRMCR1i_OMSB_SHIFT)
+#define ASRMCR1i_OMSB_MSB (1 << ASRMCR1i_OMSB_SHIFT)
+#define ASRMCR1i_OMSB_LSB (0 << ASRMCR1i_OMSB_SHIFT)
+#define ASRMCR1i_OSGN_SHIFT 1
+#define ASRMCR1i_OSGN_MASK (1 << ASRMCR1i_OSGN_SHIFT)
+#define ASRMCR1i_OSGN (1 << ASRMCR1i_OSGN_SHIFT)
+#define ASRMCR1i_OW16_SHIFT 0
+#define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT)
+#define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT)
+
+
+enum asrc_pair_index {
+ ASRC_INVALID_PAIR = -1,
+ ASRC_PAIR_A = 0,
+ ASRC_PAIR_B = 1,
+ ASRC_PAIR_C = 2,
+};
+
+#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1)
+
+enum asrc_inclk {
+ INCLK_NONE = 0x03,
+ INCLK_ESAI_RX = 0x00,
+ INCLK_SSI1_RX = 0x01,
+ INCLK_SSI2_RX = 0x02,
+ INCLK_SSI3_RX = 0x07,
+ INCLK_SPDIF_RX = 0x04,
+ INCLK_MLB_CLK = 0x05,
+ INCLK_PAD = 0x06,
+ INCLK_ESAI_TX = 0x08,
+ INCLK_SSI1_TX = 0x09,
+ INCLK_SSI2_TX = 0x0a,
+ INCLK_SSI3_TX = 0x0b,
+ INCLK_SPDIF_TX = 0x0c,
+ INCLK_ASRCK1_CLK = 0x0f,
+};
+
+enum asrc_outclk {
+ OUTCLK_NONE = 0x03,
+ OUTCLK_ESAI_TX = 0x00,
+ OUTCLK_SSI1_TX = 0x01,
+ OUTCLK_SSI2_TX = 0x02,
+ OUTCLK_SSI3_TX = 0x07,
+ OUTCLK_SPDIF_TX = 0x04,
+ OUTCLK_MLB_CLK = 0x05,
+ OUTCLK_PAD = 0x06,
+ OUTCLK_ESAI_RX = 0x08,
+ OUTCLK_SSI1_RX = 0x09,
+ OUTCLK_SSI2_RX = 0x0a,
+ OUTCLK_SSI3_RX = 0x0b,
+ OUTCLK_SPDIF_RX = 0x0c,
+ OUTCLK_ASRCK1_CLK = 0x0f,
+};
+
+#define ASRC_CLK_MAX_NUM 16
+
+enum asrc_word_width {
+ ASRC_WIDTH_24_BIT = 0,
+ ASRC_WIDTH_16_BIT = 1,
+ ASRC_WIDTH_8_BIT = 2,
+};
+
+struct asrc_config {
+ enum asrc_pair_index pair;
+ unsigned int channel_num;
+ unsigned int buffer_num;
+ unsigned int dma_buffer_size;
+ unsigned int input_sample_rate;
+ unsigned int output_sample_rate;
+ enum asrc_word_width input_word_width;
+ enum asrc_word_width output_word_width;
+ enum asrc_inclk inclk;
+ enum asrc_outclk outclk;
+};
+
+struct asrc_req {
+ unsigned int chn_num;
+ enum asrc_pair_index index;
+};
+
+struct asrc_querybuf {
+ unsigned int buffer_index;
+ unsigned int input_length;
+ unsigned int output_length;
+ unsigned long input_offset;
+ unsigned long output_offset;
+};
+
+struct asrc_convert_buffer {
+ void *input_buffer_vaddr;
+ void *output_buffer_vaddr;
+ unsigned int input_buffer_length;
+ unsigned int output_buffer_length;
+};
+
+struct asrc_status_flags {
+ enum asrc_pair_index index;
+ unsigned int overload_error;
+};
+
+enum asrc_error_status {
+ ASRC_TASK_Q_OVERLOAD = 0x01,
+ ASRC_OUTPUT_TASK_OVERLOAD = 0x02,
+ ASRC_INPUT_TASK_OVERLOAD = 0x04,
+ ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08,
+ ASRC_INPUT_BUFFER_UNDERRUN = 0x10,
+};
+
+struct dma_block {
+ dma_addr_t dma_paddr;
+ void *dma_vaddr;
+ unsigned int length;
+};
+
+/**
+ * fsl_asrc_pair: ASRC Pair private data
+ *
+ * @asrc_priv: pointer to its parent module
+ * @config: configuration profile
+ * @error: error record
+ * @index: pair index (ASRC_PAIR_A, ASRC_PAIR_B, ASRC_PAIR_C)
+ * @channels: occupied channel number
+ * @desc: input and output dma descriptors
+ * @dma_chan: inputer and output DMA channels
+ * @dma_data: private dma data
+ * @pos: hardware pointer position
+ * @private: pair private area
+ */
+struct fsl_asrc_pair {
+ struct fsl_asrc *asrc_priv;
+ struct asrc_config *config;
+ unsigned int error;
+
+ enum asrc_pair_index index;
+ unsigned int channels;
+
+ struct dma_async_tx_descriptor *desc[2];
+ struct dma_chan *dma_chan[2];
+ struct imx_dma_data dma_data;
+ unsigned int pos;
+
+ void *private;
+};
+
+/**
+ * fsl_asrc_pair: ASRC private data
+ *
+ * @dma_params_rx: DMA parameters for receive channel
+ * @dma_params_tx: DMA parameters for transmit channel
+ * @pdev: platform device pointer
+ * @regmap: regmap handler
+ * @paddr: physical address to the base address of registers
+ * @mem_clk: clock source to access register
+ * @ipg_clk: clock source to drive peripheral
+ * @asrck_clk: clock sources to driver ASRC internal logic
+ * @lock: spin lock for resource protection
+ * @pair: pair pointers
+ * @channel_bits: width of ASRCNCR register for each pair
+ * @channel_avail: non-occupied channel numbers
+ * @asrc_rate: default sample rate for ASoC Back-Ends
+ * @asrc_width: default sample width for ASoC Back-Ends
+ * @name: driver name
+ */
+struct fsl_asrc {
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct snd_dmaengine_dai_dma_data dma_params_tx;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ unsigned long paddr;
+ struct clk *mem_clk;
+ struct clk *ipg_clk;
+ struct clk *asrck_clk[ASRC_CLK_MAX_NUM];
+ spinlock_t lock;
+
+ struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM];
+ unsigned int channel_bits;
+ unsigned int channel_avail;
+
+ int asrc_rate;
+ int asrc_width;
+
+ char name[32];
+};
+
+extern struct snd_soc_platform_driver fsl_asrc_platform;
+struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir);
+#endif /* _FSL_ASRC_H */
diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c
new file mode 100644
index 0000000..5b1e73e
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc_dma.c
@@ -0,0 +1,386 @@
+/*
+ * Freescale ASRC ALSA SoC Platform (DMA) driver
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <nicoleotsuka@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/platform_data/dma-imx.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_asrc.h"
+
+#define FSL_ASRC_DMABUF_SIZE (256 * 1024)
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = 65535, /* Limited by SDMA engine */
+ .periods_min = 2,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+ if (!imx_dma_is_general_purpose(chan))
+ return false;
+
+ chan->private = param;
+
+ return true;
+}
+
+static void fsl_asrc_dma_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+
+ pair->pos += snd_pcm_lib_period_bytes(substream);
+ if (pair->pos >= snd_pcm_lib_buffer_bytes(substream))
+ pair->pos = 0;
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream)
+{
+ u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ struct device *dev = rtd->platform->dev;
+ unsigned long flags = DMA_CTRL_ACK;
+
+ /* Prepare and submit Front-End DMA channel */
+ if (!substream->runtime->no_period_wakeup)
+ flags |= DMA_PREP_INTERRUPT;
+
+ pair->pos = 0;
+ pair->desc[!dir] = dmaengine_prep_dma_cyclic(
+ pair->dma_chan[!dir], runtime->dma_addr,
+ snd_pcm_lib_buffer_bytes(substream),
+ snd_pcm_lib_period_bytes(substream),
+ dir == OUT ? DMA_TO_DEVICE : DMA_FROM_DEVICE, flags);
+ if (!pair->desc[!dir]) {
+ dev_err(dev, "failed to prepare slave DMA for Front-End\n");
+ return -ENOMEM;
+ }
+
+ pair->desc[!dir]->callback = fsl_asrc_dma_complete;
+ pair->desc[!dir]->callback_param = substream;
+
+ dmaengine_submit(pair->desc[!dir]);
+
+ /* Prepare and submit Back-End DMA channel */
+ pair->desc[dir] = dmaengine_prep_dma_cyclic(
+ pair->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0);
+ if (!pair->desc[dir]) {
+ dev_err(dev, "failed to prepare slave DMA for Back-End\n");
+ return -ENOMEM;
+ }
+
+ dmaengine_submit(pair->desc[dir]);
+
+ return 0;
+}
+
+static int fsl_asrc_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = fsl_asrc_dma_prepare_and_submit(substream);
+ if (ret)
+ return ret;
+ dma_async_issue_pending(pair->dma_chan[IN]);
+ dma_async_issue_pending(pair->dma_chan[OUT]);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dmaengine_terminate_all(pair->dma_chan[OUT]);
+ dmaengine_terminate_all(pair->dma_chan[IN]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL;
+ struct snd_dmaengine_dai_dma_data *dma_params_be = NULL;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct dma_slave_config config_fe, config_be;
+ enum asrc_pair_index index = pair->index;
+ struct device *dev = rtd->platform->dev;
+ int stream = substream->stream;
+ struct imx_dma_data *tmp_data;
+ struct snd_soc_dpcm *dpcm;
+ struct dma_chan *tmp_chan;
+ struct device *dev_be;
+ u8 dir = tx ? OUT : IN;
+ dma_cap_mask_t mask;
+ int ret;
+
+ /* Fetch the Back-End dma_data from DPCM */
+ list_for_each_entry(dpcm, &rtd->dpcm[stream].be_clients, list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_pcm_substream *substream_be;
+ struct snd_soc_dai *dai = be->cpu_dai;
+
+ if (dpcm->fe != rtd)
+ continue;
+
+ substream_be = snd_soc_dpcm_get_substream(be, stream);
+ dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be);
+ dev_be = dai->dev;
+ break;
+ }
+
+ if (!dma_params_be) {
+ dev_err(dev, "failed to get the substream of Back-End\n");
+ return -EINVAL;
+ }
+
+ /* Override dma_data of the Front-End and config its dmaengine */
+ dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ dma_params_fe->addr = asrc_priv->paddr + REG_ASRDx(!dir, index);
+ dma_params_fe->maxburst = dma_params_be->maxburst;
+
+ pair->dma_chan[!dir] = fsl_asrc_get_dma_channel(pair, !dir);
+ if (!pair->dma_chan[!dir]) {
+ dev_err(dev, "failed to request DMA channel\n");
+ return -EINVAL;
+ }
+
+ memset(&config_fe, 0, sizeof(config_fe));
+ ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe);
+ if (ret) {
+ dev_err(dev, "failed to prepare DMA config for Front-End\n");
+ return ret;
+ }
+
+ ret = dmaengine_slave_config(pair->dma_chan[!dir], &config_fe);
+ if (ret) {
+ dev_err(dev, "failed to config DMA channel for Front-End\n");
+ return ret;
+ }
+
+ /* Request and config DMA channel for Back-End */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_CYCLIC, mask);
+
+ /* Get DMA request of Back-End */
+ tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx");
+ tmp_data = tmp_chan->private;
+ pair->dma_data.dma_request = tmp_data->dma_request;
+ dma_release_channel(tmp_chan);
+
+ /* Get DMA request of Front-End */
+ tmp_chan = fsl_asrc_get_dma_channel(pair, dir);
+ tmp_data = tmp_chan->private;
+ pair->dma_data.dma_request2 = tmp_data->dma_request;
+ pair->dma_data.peripheral_type = tmp_data->peripheral_type;
+ pair->dma_data.priority = tmp_data->priority;
+ dma_release_channel(tmp_chan);
+
+ pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data);
+ if (!pair->dma_chan[dir]) {
+ dev_err(dev, "failed to request DMA channel for Back-End\n");
+ return -EINVAL;
+ }
+
+ if (asrc_priv->asrc_width == 16)
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ else
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ config_be.direction = DMA_DEV_TO_DEV;
+ config_be.src_addr_width = buswidth;
+ config_be.src_maxburst = dma_params_be->maxburst;
+ config_be.dst_addr_width = buswidth;
+ config_be.dst_maxburst = dma_params_be->maxburst;
+
+ if (tx) {
+ config_be.src_addr = asrc_priv->paddr + REG_ASRDO(index);
+ config_be.dst_addr = dma_params_be->addr;
+ } else {
+ config_be.dst_addr = asrc_priv->paddr + REG_ASRDI(index);
+ config_be.src_addr = dma_params_be->addr;
+ }
+
+ ret = dmaengine_slave_config(pair->dma_chan[dir], &config_be);
+ if (ret) {
+ dev_err(dev, "failed to config DMA channel for Back-End\n");
+ return ret;
+ }
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static int fsl_asrc_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ if (pair->dma_chan[IN])
+ dma_release_channel(pair->dma_chan[IN]);
+
+ if (pair->dma_chan[OUT])
+ dma_release_channel(pair->dma_chan[OUT]);
+
+ pair->dma_chan[IN] = NULL;
+ pair->dma_chan[OUT] = NULL;
+
+ return 0;
+}
+
+static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct device *dev = rtd->platform->dev;
+ struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+ struct fsl_asrc_pair *pair;
+
+ pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL);
+ if (!pair) {
+ dev_err(dev, "failed to allocate pair\n");
+ return -ENOMEM;
+ }
+
+ pair->asrc_priv = asrc_priv;
+
+ runtime->private_data = pair;
+
+ snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+ return 0;
+}
+
+static int fsl_asrc_dma_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+
+ if (pair && asrc_priv->pair[pair->index] == pair)
+ asrc_priv->pair[pair->index] = NULL;
+
+ kfree(pair);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t fsl_asrc_dma_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+
+ return bytes_to_frames(substream->runtime, pair->pos);
+}
+
+static struct snd_pcm_ops fsl_asrc_dma_pcm_ops = {
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = fsl_asrc_dma_hw_params,
+ .hw_free = fsl_asrc_dma_hw_free,
+ .trigger = fsl_asrc_dma_trigger,
+ .open = fsl_asrc_dma_startup,
+ .close = fsl_asrc_dma_shutdown,
+ .pointer = fsl_asrc_dma_pcm_pointer,
+};
+
+static int fsl_asrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm *pcm = rtd->pcm;
+ int ret, i;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(card->dev, "failed to set DMA mask\n");
+ return ret;
+ }
+
+ for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) {
+ substream = pcm->streams[i].substream;
+ if (!substream)
+ continue;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+ FSL_ASRC_DMABUF_SIZE, &substream->dma_buffer);
+ if (ret) {
+ dev_err(card->dev, "failed to allocate DMA buffer\n");
+ goto err;
+ }
+ }
+
+ return 0;
+
+err:
+ if (--i == 0 && pcm->streams[i].substream)
+ snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer);
+
+ return ret;
+}
+
+static void fsl_asrc_dma_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ int i;
+
+ for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) {
+ substream = pcm->streams[i].substream;
+ if (!substream)
+ continue;
+
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+ substream->dma_buffer.addr = 0;
+ }
+}
+
+struct snd_soc_platform_driver fsl_asrc_platform = {
+ .ops = &fsl_asrc_dma_pcm_ops,
+ .pcm_new = fsl_asrc_dma_pcm_new,
+ .pcm_free = fsl_asrc_dma_pcm_free,
+};
+EXPORT_SYMBOL_GPL(fsl_asrc_platform);
--
1.8.4
^ permalink raw reply related
* [PATCH v6 1/2] ARM: imx: Add the secondary request into the structure for imx-sdma
From: Nicolin Chen @ 2014-07-29 10:08 UTC (permalink / raw)
To: broonie
Cc: mark.rutland, devicetree, alsa-devel, b42378, timur, pawel.moll,
ijc+devicetree, tiwai, linux-kernel, rdunlap, b02247, lgirdwood,
perex, varkabhadram, robh+dt, galak, grant.likely, tklauser,
shawn.guo, linuxppc-dev
In-Reply-To: <cover.1406628283.git.nicoleotsuka@gmail.com>
SDMA supports device to device (per_2_per) scripts to handle DMA transfering
between two peripheral devices. The per_2_per script, however, needs two dma
requests from two sides while the current structure only defined one request.
So this patch just simply adds the secondary request so as to let SDMA and
its user to add its implementation later.
[ Both change in the SDMA driver and its users like Freescale ASRC ASoC driver
should be taken along with this change in order to truly support per_2_per
sciprts. However, we here make an expediency by adding this first so that
we can add either side later since this patch won't break any function and
meanwhile it can make merge window more smoothly: we don't need to apply the
change inside dmaengine branch via ASoC tree any more. -- Nicolin ]
Signed-off-by: Nicolin Chen <nicoleotsuka@gmail.com>
Acked-by: Shawn Guo <shawn.guo@linaro.org>
---
include/linux/platform_data/dma-imx.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/linux/platform_data/dma-imx.h b/include/linux/platform_data/dma-imx.h
index bcbc6c3..d05542a 100644
--- a/include/linux/platform_data/dma-imx.h
+++ b/include/linux/platform_data/dma-imx.h
@@ -50,6 +50,7 @@ enum imx_dma_prio {
struct imx_dma_data {
int dma_request; /* DMA request line */
+ int dma_request2; /* secondary DMA request line */
enum sdma_peripheral_type peripheral_type;
int priority;
};
--
1.8.4
^ permalink raw reply related
* Re: [PATCH v3 2/2] ASoC: fsl_asrc: Add ASRC ASoC CPU DAI and platform drivers
From: Nicolin Chen @ 2014-07-29 10:13 UTC (permalink / raw)
To: Mark Rutland
Cc: devicetree@vger.kernel.org, alsa-devel@alsa-project.org,
b42378@freescale.com, timur@tabi.org, Pawel Moll,
ijc+devicetree@hellion.org.uk, tiwai@suse.de,
linux-kernel@vger.kernel.org, rdunlap@infradead.org,
b02247@freescale.com, lgirdwood@gmail.com, robh+dt@kernel.org,
perex@perex.cz, Nicolin Chen, broonie@kernel.org,
galak@codeaurora.org, grant.likely@linaro.org,
tklauser@distanz.ch, shawn.guo@linaro.org,
linuxppc-dev@lists.ozlabs.org
In-Reply-To: <20140729094613.GK2576@leverpostej>
On Tue, Jul 29, 2014 at 10:46:13AM +0100, Mark Rutland wrote:
> > + - big-endian : If this property is absent, the native endian mode will
> > + be in use as default, or the big endian mode will be in use
> > + for all the device registers.
>
> Native endian is meaningless. If a CPU supports both BE and LE, there is
> no native endianness. The endianness of the kernel is dynamic while the
> endianness of registers in HW is fixed.
>
> Just choose an endianness to assume by default (presumably little). That
> way this describes the HW and always works with a kernel of arbitrary
> endianness.
Thank you for the comments.
I just revised it by using 'little endian as default' and sent the patch v6.
Please take a look at the new version.
Thanks again,
Nicolin
^ permalink raw reply
* Re: [PATCH] powerpc: thp: Add write barrier after updating the valid bit
From: Aneesh Kumar K.V @ 2014-07-29 10:37 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: linuxppc-dev, paulus
In-Reply-To: <1406617244.4935.81.camel@pasglop>
Benjamin Herrenschmidt <benh@kernel.crashing.org> writes:
> On Tue, 2014-07-29 at 12:25 +0530, Aneesh Kumar K.V wrote:
>> We want to make sure for usage like below we don't reorder the load.
>>
>> if (pmd_trans_huge(*pmdp)){
>>
>> get_hpte_slot_array(pmdp)
>> }
>
> Shouldn't we also make sure that we don't have lock set ? (In case it's
> in the middle of being updated).
The reace against withdraw is not a real problem. We use
get_hpte_slot_array in huge page invalidate and hash page. In the first
case we are holding pmd_trans_huge_lock and in the later we have the
_PAGE_BUSY check.
-aneesh
^ permalink raw reply
* [PATCH 0/3] Add HMI handling in Linux host.
From: Mahesh J Salgaonkar @ 2014-07-29 13:09 UTC (permalink / raw)
To: linuxppc-dev, Benjamin Herrenschmidt
This patch series implements HMI interrupt handling in Linux. First patch
implements basic infrastructure to handle HMI in Linux host. Second patch
invokes opal call to handle hmi in opal firmware and retrieve HMI event
generated by opal. Third patch handles HMI received on cpus in sleep/nap mode.
This patchset have been tested with following scenario:
a. SMT on mode
b. SMT off mode without any guest
c. SMT off + multiple guest
c. SMT off + multiple guest running 'find /' command
Thanks,
-Mahesh.
---
Mahesh Salgaonkar (3):
powerpc/book3s: Add basic infrastructure to handle HMI in Linux.
powerpc/powernv: Invoke opal call to handle hmi.
powerpc/book3s: handle HMIs for cpus in nap mode.
arch/powerpc/include/asm/exception-64s.h | 2
arch/powerpc/include/asm/hardirq.h | 1
arch/powerpc/include/asm/hw_irq.h | 1
arch/powerpc/include/asm/kvm_asm.h | 1
arch/powerpc/include/asm/machdep.h | 4 +
arch/powerpc/include/asm/opal.h | 49 ++++++
arch/powerpc/include/asm/paca.h | 1
arch/powerpc/kernel/entry_64.S | 5 +
arch/powerpc/kernel/exceptions-64s.S | 66 ++++++++
arch/powerpc/kernel/idle_power7.S | 32 ++++
arch/powerpc/kernel/irq.c | 14 ++
arch/powerpc/kernel/traps.c | 24 +++
arch/powerpc/kvm/book3s_hv_rmhandlers.S | 6 +
arch/powerpc/platforms/powernv/Makefile | 2
arch/powerpc/platforms/powernv/opal-hmi.c | 188 ++++++++++++++++++++++++
arch/powerpc/platforms/powernv/opal-wrappers.S | 1
arch/powerpc/platforms/powernv/opal.c | 43 +++++
arch/powerpc/platforms/powernv/setup.c | 2
18 files changed, 435 insertions(+), 7 deletions(-)
create mode 100644 arch/powerpc/platforms/powernv/opal-hmi.c
--
Signature
^ permalink raw reply
* [PATCH 1/3] powerpc/book3s: Add basic infrastructure to handle HMI in Linux.
From: Mahesh J Salgaonkar @ 2014-07-29 13:10 UTC (permalink / raw)
To: linuxppc-dev, Benjamin Herrenschmidt
In-Reply-To: <20140729130900.13492.85976.stgit@mars>
From: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
Handle Hypervisor Maintenance Interrupt (HMI) in Linux. This patch implements
basic infrastructure to handle HMI in Linux host. The design is to invoke
opal handle hmi in real mode for recovery and set irq_pending when we hit HMI.
During check_irq_replay pull opal hmi event and print hmi info on console.
Signed-off-by: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
---
arch/powerpc/include/asm/exception-64s.h | 2 +
arch/powerpc/include/asm/hardirq.h | 1
arch/powerpc/include/asm/hw_irq.h | 1
arch/powerpc/include/asm/kvm_asm.h | 1
arch/powerpc/include/asm/machdep.h | 4 ++
arch/powerpc/include/asm/opal.h | 2 +
arch/powerpc/kernel/entry_64.S | 5 ++
arch/powerpc/kernel/exceptions-64s.S | 66 +++++++++++++++++++++++++++++-
arch/powerpc/kernel/irq.c | 14 ++++++
arch/powerpc/kernel/traps.c | 24 +++++++++++
arch/powerpc/kvm/book3s_hv_rmhandlers.S | 6 +++
arch/powerpc/platforms/powernv/opal.c | 14 ++++++
arch/powerpc/platforms/powernv/setup.c | 2 +
13 files changed, 139 insertions(+), 3 deletions(-)
diff --git a/arch/powerpc/include/asm/exception-64s.h b/arch/powerpc/include/asm/exception-64s.h
index 8f35cd7..f99ea33 100644
--- a/arch/powerpc/include/asm/exception-64s.h
+++ b/arch/powerpc/include/asm/exception-64s.h
@@ -425,6 +425,8 @@ label##_relon_hv: \
#define SOFTEN_VALUE_0xa00 PACA_IRQ_DBELL
#define SOFTEN_VALUE_0xe80 PACA_IRQ_DBELL
#define SOFTEN_VALUE_0xe82 PACA_IRQ_DBELL
+#define SOFTEN_VALUE_0xe60 PACA_IRQ_HMI
+#define SOFTEN_VALUE_0xe62 PACA_IRQ_HMI
#define __SOFTEN_TEST(h, vec) \
lbz r10,PACASOFTIRQEN(r13); \
diff --git a/arch/powerpc/include/asm/hardirq.h b/arch/powerpc/include/asm/hardirq.h
index 418fb65..1bbb301 100644
--- a/arch/powerpc/include/asm/hardirq.h
+++ b/arch/powerpc/include/asm/hardirq.h
@@ -11,6 +11,7 @@ typedef struct {
unsigned int pmu_irqs;
unsigned int mce_exceptions;
unsigned int spurious_irqs;
+ unsigned int hmi_exceptions;
#ifdef CONFIG_PPC_DOORBELL
unsigned int doorbell_irqs;
#endif
diff --git a/arch/powerpc/include/asm/hw_irq.h b/arch/powerpc/include/asm/hw_irq.h
index 10be1dd..b59ac27 100644
--- a/arch/powerpc/include/asm/hw_irq.h
+++ b/arch/powerpc/include/asm/hw_irq.h
@@ -25,6 +25,7 @@
#define PACA_IRQ_EE 0x04
#define PACA_IRQ_DEC 0x08 /* Or FIT */
#define PACA_IRQ_EE_EDGE 0x10 /* BookE only */
+#define PACA_IRQ_HMI 0x20
#endif /* CONFIG_PPC64 */
diff --git a/arch/powerpc/include/asm/kvm_asm.h b/arch/powerpc/include/asm/kvm_asm.h
index 9601741..ecf7e13 100644
--- a/arch/powerpc/include/asm/kvm_asm.h
+++ b/arch/powerpc/include/asm/kvm_asm.h
@@ -98,6 +98,7 @@
#define BOOK3S_INTERRUPT_H_DATA_STORAGE 0xe00
#define BOOK3S_INTERRUPT_H_INST_STORAGE 0xe20
#define BOOK3S_INTERRUPT_H_EMUL_ASSIST 0xe40
+#define BOOK3S_INTERRUPT_HMI 0xe60
#define BOOK3S_INTERRUPT_H_DOORBELL 0xe80
#define BOOK3S_INTERRUPT_PERFMON 0xf00
#define BOOK3S_INTERRUPT_ALTIVEC 0xf20
diff --git a/arch/powerpc/include/asm/machdep.h b/arch/powerpc/include/asm/machdep.h
index f92b0b5..06bcf5bd 100644
--- a/arch/powerpc/include/asm/machdep.h
+++ b/arch/powerpc/include/asm/machdep.h
@@ -174,6 +174,10 @@ struct machdep_calls {
/* Exception handlers */
int (*system_reset_exception)(struct pt_regs *regs);
int (*machine_check_exception)(struct pt_regs *regs);
+ int (*handle_hmi_exception)(struct pt_regs *regs);
+
+ /* Early exception handlers called in realmode */
+ int (*hmi_exception_early)(struct pt_regs *regs);
/* Called during machine check exception to retrive fixup address. */
bool (*mce_check_early_recovery)(struct pt_regs *regs);
diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
index 0da1dbd..dd1cf8d 100644
--- a/arch/powerpc/include/asm/opal.h
+++ b/arch/powerpc/include/asm/opal.h
@@ -902,6 +902,8 @@ extern void opal_msglog_init(void);
extern int opal_machine_check(struct pt_regs *regs);
extern bool opal_mce_check_early_recovery(struct pt_regs *regs);
+extern int opal_hmi_exception_early(struct pt_regs *regs);
+extern int opal_handle_hmi_exception(struct pt_regs *regs);
extern void opal_shutdown(void);
extern int opal_resync_timebase(void);
diff --git a/arch/powerpc/kernel/entry_64.S b/arch/powerpc/kernel/entry_64.S
index 6528c5e..e9423a8 100644
--- a/arch/powerpc/kernel/entry_64.S
+++ b/arch/powerpc/kernel/entry_64.S
@@ -919,6 +919,11 @@ restore_check_irq_replay:
addi r3,r1,STACK_FRAME_OVERHEAD;
bl do_IRQ
b ret_from_except
+1: cmpwi cr0,r3,0xe60
+ bne 1f
+ addi r3,r1,STACK_FRAME_OVERHEAD;
+ bl handle_hmi_exception
+ b ret_from_except
1: cmpwi cr0,r3,0x900
bne 1f
addi r3,r1,STACK_FRAME_OVERHEAD;
diff --git a/arch/powerpc/kernel/exceptions-64s.S b/arch/powerpc/kernel/exceptions-64s.S
index a7d36b1..141ce6b 100644
--- a/arch/powerpc/kernel/exceptions-64s.S
+++ b/arch/powerpc/kernel/exceptions-64s.S
@@ -339,7 +339,7 @@ emulation_assist_trampoline:
hv_exception_trampoline:
SET_SCRATCH0(r13)
EXCEPTION_PROLOG_0(PACA_EXGEN)
- b hmi_exception_hv
+ b hmi_exception_early
. = 0xe80
hv_doorbell_trampoline:
@@ -621,8 +621,64 @@ END_FTR_SECTION_IFSET(CPU_FTR_CFAR)
KVM_HANDLER(PACA_EXGEN, EXC_HV, 0xe22)
STD_EXCEPTION_HV_OOL(0xe42, emulation_assist)
KVM_HANDLER(PACA_EXGEN, EXC_HV, 0xe42)
- STD_EXCEPTION_HV_OOL(0xe62, hmi_exception) /* need to flush cache ? */
+ MASKABLE_EXCEPTION_HV_OOL(0xe62, hmi_exception)
KVM_HANDLER(PACA_EXGEN, EXC_HV, 0xe62)
+
+ .globl hmi_exception_early
+hmi_exception_early:
+ EXCEPTION_PROLOG_1(PACA_EXGEN, NOTEST, 0xe60)
+ mr r10,r1 /* Save r1 */
+ ld r1,PACAEMERGSP(r13) /* Use emergency stack */
+ subi r1,r1,INT_FRAME_SIZE /* alloc stack frame */
+ std r9,_CCR(r1) /* save CR in stackframe */
+ mfspr r11,SPRN_HSRR0 /* Save HSRR0 */
+ std r11,_NIP(r1) /* save HSRR0 in stackframe */
+ mfspr r12,SPRN_HSRR1 /* Save SRR1 */
+ std r12,_MSR(r1) /* save SRR1 in stackframe */
+ std r10,0(r1) /* make stack chain pointer */
+ std r0,GPR0(r1) /* save r0 in stackframe */
+ std r10,GPR1(r1) /* save r1 in stackframe */
+ EXCEPTION_PROLOG_COMMON_2(PACA_EXGEN)
+ EXCEPTION_PROLOG_COMMON_3(0xe60)
+ addi r3,r1,STACK_FRAME_OVERHEAD
+ bl hmi_exception_realmode
+ /* Windup the stack. */
+ /* Clear MSR_RI before setting SRR0 and SRR1. */
+ li r0,MSR_RI
+ mfmsr r9 /* get MSR value */
+ andc r9,r9,r0
+ mtmsrd r9,1 /* Clear MSR_RI */
+ /* Move original HSRR0 and HSRR1 into the respective regs */
+ ld r9,_MSR(r1)
+ mtspr SPRN_HSRR1,r9
+ ld r3,_NIP(r1)
+ mtspr SPRN_HSRR0,r3
+ ld r9,_CTR(r1)
+ mtctr r9
+ ld r9,_XER(r1)
+ mtxer r9
+ ld r9,_LINK(r1)
+ mtlr r9
+ REST_GPR(0, r1)
+ REST_8GPRS(2, r1)
+ REST_GPR(10, r1)
+ ld r11,_CCR(r1)
+ mtcr r11
+ REST_GPR(11, r1)
+ REST_2GPRS(12, r1)
+ /* restore original r1. */
+ ld r1,GPR1(r1)
+
+ /*
+ * Go to virtual mode and pull the HMI event information from
+ * firmware.
+ */
+ .globl hmi_exception_after_realmode
+hmi_exception_after_realmode:
+ SET_SCRATCH0(r13)
+ EXCEPTION_PROLOG_0(PACA_EXGEN)
+ b hmi_exception_hv
+
MASKABLE_EXCEPTION_HV_OOL(0xe82, h_doorbell)
KVM_HANDLER(PACA_EXGEN, EXC_HV, 0xe82)
@@ -643,6 +699,8 @@ END_FTR_SECTION_IFSET(CPU_FTR_CFAR)
* - If it was a decrementer interrupt, we bump the dec to max and and return.
* - If it was a doorbell we return immediately since doorbells are edge
* triggered and won't automatically refire.
+ * - If it was a HMI we return immediately since we handled it in realmode
+ * and it won't refire.
* - else we hard disable and return.
* This is called with r10 containing the value to OR to the paca field.
*/
@@ -660,6 +718,8 @@ masked_##_H##interrupt: \
b 2f; \
1: cmpwi r10,PACA_IRQ_DBELL; \
beq 2f; \
+ cmpwi r10,PACA_IRQ_HMI; \
+ beq 2f; \
mfspr r10,SPRN_##_H##SRR1; \
rldicl r10,r10,48,1; /* clear MSR_EE */ \
rotldi r10,r10,16; \
@@ -799,7 +859,7 @@ kvmppc_skip_Hinterrupt:
STD_EXCEPTION_COMMON(0xd00, single_step, single_step_exception)
STD_EXCEPTION_COMMON(0xe00, trap_0e, unknown_exception)
STD_EXCEPTION_COMMON(0xe40, emulation_assist, emulation_assist_interrupt)
- STD_EXCEPTION_COMMON(0xe60, hmi_exception, unknown_exception)
+ STD_EXCEPTION_COMMON_ASYNC(0xe60, hmi_exception, handle_hmi_exception)
#ifdef CONFIG_PPC_DOORBELL
STD_EXCEPTION_COMMON_ASYNC(0xe80, h_doorbell, doorbell_exception)
#else
diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
index 248ee7e..4c5891d 100644
--- a/arch/powerpc/kernel/irq.c
+++ b/arch/powerpc/kernel/irq.c
@@ -189,6 +189,11 @@ notrace unsigned int __check_irq_replay(void)
}
#endif /* CONFIG_PPC_BOOK3E */
+ /* Check if an hypervisor Maintenance interrupt happened */
+ local_paca->irq_happened &= ~PACA_IRQ_HMI;
+ if (happened & PACA_IRQ_HMI)
+ return 0xe60;
+
/* There should be nothing left ! */
BUG_ON(local_paca->irq_happened != 0);
@@ -377,6 +382,14 @@ int arch_show_interrupts(struct seq_file *p, int prec)
seq_printf(p, "%10u ", per_cpu(irq_stat, j).mce_exceptions);
seq_printf(p, " Machine check exceptions\n");
+ if (cpu_has_feature(CPU_FTR_HVMODE)) {
+ seq_printf(p, "%*s: ", prec, "HMI");
+ for_each_online_cpu(j)
+ seq_printf(p, "%10u ",
+ per_cpu(irq_stat, j).hmi_exceptions);
+ seq_printf(p, " Hypervisor Maintenance Interrupts\n");
+ }
+
#ifdef CONFIG_PPC_DOORBELL
if (cpu_has_feature(CPU_FTR_DBELL)) {
seq_printf(p, "%*s: ", prec, "DBL");
@@ -400,6 +413,7 @@ u64 arch_irq_stat_cpu(unsigned int cpu)
sum += per_cpu(irq_stat, cpu).mce_exceptions;
sum += per_cpu(irq_stat, cpu).spurious_irqs;
sum += per_cpu(irq_stat, cpu).timer_irqs_others;
+ sum += per_cpu(irq_stat, cpu).hmi_exceptions;
#ifdef CONFIG_PPC_DOORBELL
sum += per_cpu(irq_stat, cpu).doorbell_irqs;
#endif
diff --git a/arch/powerpc/kernel/traps.c b/arch/powerpc/kernel/traps.c
index 239f1cd..0ace286 100644
--- a/arch/powerpc/kernel/traps.c
+++ b/arch/powerpc/kernel/traps.c
@@ -302,6 +302,16 @@ long machine_check_early(struct pt_regs *regs)
return handled;
}
+long hmi_exception_realmode(struct pt_regs *regs)
+{
+ __get_cpu_var(irq_stat).hmi_exceptions++;
+
+ if (ppc_md.hmi_exception_early)
+ ppc_md.hmi_exception_early(regs);
+
+ return 0;
+}
+
#endif
/*
@@ -738,6 +748,20 @@ void SMIException(struct pt_regs *regs)
die("System Management Interrupt", regs, SIGABRT);
}
+void handle_hmi_exception(struct pt_regs *regs)
+{
+ struct pt_regs *old_regs;
+
+ old_regs = set_irq_regs(regs);
+ irq_enter();
+
+ if (ppc_md.handle_hmi_exception)
+ ppc_md.handle_hmi_exception(regs);
+
+ irq_exit();
+ set_irq_regs(old_regs);
+}
+
void unknown_exception(struct pt_regs *regs)
{
enum ctx_state prev_state = exception_enter();
diff --git a/arch/powerpc/kvm/book3s_hv_rmhandlers.S b/arch/powerpc/kvm/book3s_hv_rmhandlers.S
index 868347e..0de9309 100644
--- a/arch/powerpc/kvm/book3s_hv_rmhandlers.S
+++ b/arch/powerpc/kvm/book3s_hv_rmhandlers.S
@@ -159,6 +159,8 @@ END_FTR_SECTION_IFSET(CPU_FTR_ARCH_207S)
cmpwi r12, BOOK3S_INTERRUPT_EXTERNAL
BEGIN_FTR_SECTION
beq 11f
+ cmpwi cr2, r12, BOOK3S_INTERRUPT_HMI
+ beq cr2, 14f /* HMI check */
END_FTR_SECTION_IFSET(CPU_FTR_ARCH_206)
/* RFI into the highmem handler, or branch to interrupt handler */
@@ -179,6 +181,10 @@ END_FTR_SECTION_IFSET(CPU_FTR_ARCH_206)
13: b machine_check_fwnmi
+14: mtspr SPRN_HSRR0, r8
+ mtspr SPRN_HSRR1, r7
+ b hmi_exception_after_realmode
+
kvmppc_primary_no_guest:
/* We handle this much like a ceded vcpu */
/* set our bit in napping_threads */
diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
index 1999756..af4c5ac 100644
--- a/arch/powerpc/platforms/powernv/opal.c
+++ b/arch/powerpc/platforms/powernv/opal.c
@@ -513,6 +513,20 @@ int opal_machine_check(struct pt_regs *regs)
return 0;
}
+/* Early hmi handler called in real mode. */
+int opal_hmi_exception_early(struct pt_regs *regs)
+{
+ /* TODO: Call opal hmi handler. */
+ return 0;
+}
+
+/* HMI exception handler called in virtual mode during check_irq_replay. */
+int opal_handle_hmi_exception(struct pt_regs *regs)
+{
+ /* TODO: Retrive and print HMI event from OPAL. */
+ return 0;
+}
+
static uint64_t find_recovery_address(uint64_t nip)
{
int i;
diff --git a/arch/powerpc/platforms/powernv/setup.c b/arch/powerpc/platforms/powernv/setup.c
index d9b88fa..5a0e2dc 100644
--- a/arch/powerpc/platforms/powernv/setup.c
+++ b/arch/powerpc/platforms/powernv/setup.c
@@ -264,6 +264,8 @@ static void __init pnv_setup_machdep_opal(void)
ppc_md.halt = pnv_halt;
ppc_md.machine_check_exception = opal_machine_check;
ppc_md.mce_check_early_recovery = opal_mce_check_early_recovery;
+ ppc_md.hmi_exception_early = opal_hmi_exception_early;
+ ppc_md.handle_hmi_exception = opal_handle_hmi_exception;
}
#ifdef CONFIG_PPC_POWERNV_RTAS
^ permalink raw reply related
* [PATCH 2/3] powerpc/powernv: Invoke opal call to handle hmi.
From: Mahesh J Salgaonkar @ 2014-07-29 13:10 UTC (permalink / raw)
To: linuxppc-dev, Benjamin Herrenschmidt
In-Reply-To: <20140729130900.13492.85976.stgit@mars>
From: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
When we hit the HMI in Linux, invoke opal call to handle/recover from HMI
errors in real mode and then in virtual mode during check_irq_replay()
invoke opal_poll_events()/opal_do_notifier() to retrieve HMI event from
OPAL and act accordingly.
Now that we are ready to handle HMI interrupt directly in linux, remove
the HMI interrupt registration with firmware.
Signed-off-by: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
---
arch/powerpc/include/asm/opal.h | 47 ++++++
arch/powerpc/include/asm/paca.h | 1
arch/powerpc/platforms/powernv/Makefile | 2
arch/powerpc/platforms/powernv/opal-hmi.c | 188 ++++++++++++++++++++++++
arch/powerpc/platforms/powernv/opal-wrappers.S | 1
arch/powerpc/platforms/powernv/opal.c | 35 ++++
6 files changed, 267 insertions(+), 7 deletions(-)
create mode 100644 arch/powerpc/platforms/powernv/opal-hmi.c
diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
index dd1cf8d..b81b99e 100644
--- a/arch/powerpc/include/asm/opal.h
+++ b/arch/powerpc/include/asm/opal.h
@@ -147,6 +147,7 @@ struct opal_sg_list {
#define OPAL_SET_PARAM 90
#define OPAL_DUMP_RESEND 91
#define OPAL_DUMP_INFO2 94
+#define OPAL_HANDLE_HMI 98
#ifndef __ASSEMBLY__
@@ -240,6 +241,7 @@ enum OpalMessageType {
OPAL_MSG_MEM_ERR,
OPAL_MSG_EPOW,
OPAL_MSG_SHUTDOWN,
+ OPAL_MSG_HMI_EVT,
OPAL_MSG_TYPE_MAX,
};
@@ -502,6 +504,50 @@ struct OpalMemoryErrorData {
} u;
};
+/* HMI interrupt event */
+enum OpalHMI_Version {
+ OpalHMIEvt_V1 = 1,
+};
+
+enum OpalHMI_Severity {
+ OpalHMI_SEV_NO_ERROR = 0,
+ OpalHMI_SEV_WARNING = 1,
+ OpalHMI_SEV_ERROR_SYNC = 2,
+ OpalHMI_SEV_FATAL = 3,
+};
+
+enum OpalHMI_Disposition {
+ OpalHMI_DISPOSITION_RECOVERED = 0,
+ OpalHMI_DISPOSITION_NOT_RECOVERED = 1,
+};
+
+enum OpalHMI_ErrType {
+ OpalHMI_ERROR_MALFUNC_ALERT = 0,
+ OpalHMI_ERROR_PROC_RECOV_DONE,
+ OpalHMI_ERROR_PROC_RECOV_DONE_AGAIN,
+ OpalHMI_ERROR_PROC_RECOV_MASKED,
+ OpalHMI_ERROR_TFAC,
+ OpalHMI_ERROR_TFMR_PARITY,
+ OpalHMI_ERROR_HA_OVERFLOW_WARN,
+ OpalHMI_ERROR_XSCOM_FAIL,
+ OpalHMI_ERROR_XSCOM_DONE,
+ OpalHMI_ERROR_SCOM_FIR,
+ OpalHMI_ERROR_DEBUG_TRIG_FIR,
+ OpalHMI_ERROR_HYP_RESOURCE,
+};
+
+struct OpalHMIEvent {
+ uint8_t version; /* 0x00 */
+ uint8_t severity; /* 0x01 */
+ uint8_t type; /* 0x02 */
+ uint8_t disposition; /* 0x03 */
+ uint8_t reserved_1[4]; /* 0x04 */
+
+ __be64 hmer;
+ /* TFMR register. Valid only for TFAC and TFMR_PARITY error type. */
+ __be64 tfmr;
+};
+
enum {
OPAL_P7IOC_DIAG_TYPE_NONE = 0,
OPAL_P7IOC_DIAG_TYPE_RGC = 1,
@@ -860,6 +906,7 @@ int64_t opal_get_param(uint64_t token, uint32_t param_id, uint64_t buffer,
int64_t opal_set_param(uint64_t token, uint32_t param_id, uint64_t buffer,
uint64_t length);
int64_t opal_sensor_read(uint32_t sensor_hndl, int token, __be32 *sensor_data);
+int64_t opal_handle_hmi(void);
/* Internal functions */
extern int early_init_dt_scan_opal(unsigned long node, const char *uname,
diff --git a/arch/powerpc/include/asm/paca.h b/arch/powerpc/include/asm/paca.h
index bb0bd25..dd799a3 100644
--- a/arch/powerpc/include/asm/paca.h
+++ b/arch/powerpc/include/asm/paca.h
@@ -171,6 +171,7 @@ struct paca_struct {
* and already using emergency stack.
*/
u16 in_mce;
+ u8 hmi_event_available; /* HMI event is available */
#endif
/* Stuff for accurate time accounting */
diff --git a/arch/powerpc/platforms/powernv/Makefile b/arch/powerpc/platforms/powernv/Makefile
index 4ad227d..495fed0 100644
--- a/arch/powerpc/platforms/powernv/Makefile
+++ b/arch/powerpc/platforms/powernv/Makefile
@@ -1,7 +1,7 @@
obj-y += setup.o opal-wrappers.o opal.o opal-async.o
obj-y += opal-rtc.o opal-nvram.o opal-lpc.o opal-flash.o
obj-y += rng.o opal-elog.o opal-dump.o opal-sysparam.o opal-sensor.o
-obj-y += opal-msglog.o
+obj-y += opal-msglog.o opal-hmi.o
obj-$(CONFIG_SMP) += smp.o subcore.o subcore-asm.o
obj-$(CONFIG_PCI) += pci.o pci-p5ioc2.o pci-ioda.o
diff --git a/arch/powerpc/platforms/powernv/opal-hmi.c b/arch/powerpc/platforms/powernv/opal-hmi.c
new file mode 100644
index 0000000..97ac8dc
--- /dev/null
+++ b/arch/powerpc/platforms/powernv/opal-hmi.c
@@ -0,0 +1,188 @@
+/*
+ * OPAL hypervisor Maintenance interrupt handling support in PowreNV.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2014 IBM Corporation
+ * Author: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
+ */
+
+#undef DEBUG
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+
+#include <asm/opal.h>
+#include <asm/cputable.h>
+
+static int opal_hmi_handler_nb_init;
+struct OpalHmiEvtNode {
+ struct list_head list;
+ struct OpalHMIEvent hmi_evt;
+};
+static LIST_HEAD(opal_hmi_evt_list);
+static DEFINE_SPINLOCK(opal_hmi_evt_lock);
+
+static void print_hmi_event_info(struct OpalHMIEvent *hmi_evt)
+{
+ const char *level, *sevstr, *error_info;
+ static const char *hmi_error_types[] = {
+ "Malfunction Alert",
+ "Processor Recovery done",
+ "Processor recovery occurred again",
+ "Processor recovery occurred for masked error",
+ "Timer facility experienced an error",
+ "TFMR SPR is corrupted",
+ "UPS (Uniterrupted Power System) Overflow indication",
+ "An XSCOM operation failure",
+ "An XSCOM operation completed",
+ "SCOM has set a reserved FIR bit to cause recovery",
+ "Debug trigger has set a reserved FIR bit to cause recovery",
+ "A hypervisor resource error occurred"
+ };
+
+ /* Print things out */
+ if (hmi_evt->version != OpalHMIEvt_V1) {
+ pr_err("HMI Interrupt, Unknown event version %d !\n",
+ hmi_evt->version);
+ return;
+ }
+ switch (hmi_evt->severity) {
+ case OpalHMI_SEV_NO_ERROR:
+ level = KERN_INFO;
+ sevstr = "Harmless";
+ break;
+ case OpalHMI_SEV_WARNING:
+ level = KERN_WARNING;
+ sevstr = "";
+ break;
+ case OpalHMI_SEV_ERROR_SYNC:
+ level = KERN_ERR;
+ sevstr = "Severe";
+ break;
+ case OpalHMI_SEV_FATAL:
+ default:
+ level = KERN_ERR;
+ sevstr = "Fatal";
+ break;
+ }
+
+ printk("%s%s Hypervisor Maintenance interrupt [%s]\n",
+ level, sevstr,
+ hmi_evt->disposition == OpalHMI_DISPOSITION_RECOVERED ?
+ "Recovered" : "Not recovered");
+ error_info = hmi_evt->type < ARRAY_SIZE(hmi_error_types) ?
+ hmi_error_types[hmi_evt->type]
+ : "Unknown";
+ printk("%s Error detail: %s\n", level, error_info);
+ printk("%s HMER: %016llx\n", level, be64_to_cpu(hmi_evt->hmer));
+ if ((hmi_evt->type == OpalHMI_ERROR_TFAC) ||
+ (hmi_evt->type == OpalHMI_ERROR_TFMR_PARITY))
+ printk("%s TFMR: %016llx\n", level,
+ be64_to_cpu(hmi_evt->tfmr));
+}
+
+static void hmi_event_handler(struct work_struct *work)
+{
+ unsigned long flags;
+ struct OpalHMIEvent *hmi_evt;
+ struct OpalHmiEvtNode *msg_node;
+ uint8_t disposition;
+
+ spin_lock_irqsave(&opal_hmi_evt_lock, flags);
+ while (!list_empty(&opal_hmi_evt_list)) {
+ msg_node = list_entry(opal_hmi_evt_list.next,
+ struct OpalHmiEvtNode, list);
+ list_del(&msg_node->list);
+ spin_unlock_irqrestore(&opal_hmi_evt_lock, flags);
+
+ hmi_evt = (struct OpalHMIEvent *) &msg_node->hmi_evt;
+ print_hmi_event_info(hmi_evt);
+ disposition = hmi_evt->disposition;
+ kfree(msg_node);
+
+ /*
+ * Check if HMI event has been recovered or not. If not
+ * then we can't continue, invoke panic.
+ */
+ if (disposition != OpalHMI_DISPOSITION_RECOVERED)
+ panic("Unrecoverable HMI exception");
+
+ spin_lock_irqsave(&opal_hmi_evt_lock, flags);
+ }
+ spin_unlock_irqrestore(&opal_hmi_evt_lock, flags);
+}
+
+static DECLARE_WORK(hmi_event_work, hmi_event_handler);
+/*
+ * opal_handle_hmi_event - notifier handler that queues up HMI events
+ * to be preocessed later.
+ */
+static int opal_handle_hmi_event(struct notifier_block *nb,
+ unsigned long msg_type, void *msg)
+{
+ unsigned long flags;
+ struct OpalHMIEvent *hmi_evt;
+ struct opal_msg *hmi_msg = msg;
+ struct OpalHmiEvtNode *msg_node;
+
+ /* Sanity Checks */
+ if (msg_type != OPAL_MSG_HMI_EVT)
+ return 0;
+
+ /* HMI event info starts from param[0] */
+ hmi_evt = (struct OpalHMIEvent *)&hmi_msg->params[0];
+
+ /* Delay the logging of HMI events to workqueue. */
+ msg_node = kzalloc(sizeof(*msg_node), GFP_ATOMIC);
+ if (!msg_node) {
+ pr_err("HMI: out of memory, Opal message event not handled\n");
+ return -ENOMEM;
+ }
+ memcpy(&msg_node->hmi_evt, hmi_evt, sizeof(struct OpalHMIEvent));
+
+ spin_lock_irqsave(&opal_hmi_evt_lock, flags);
+ list_add(&msg_node->list, &opal_hmi_evt_list);
+ spin_unlock_irqrestore(&opal_hmi_evt_lock, flags);
+
+ schedule_work(&hmi_event_work);
+ return 0;
+}
+
+static struct notifier_block opal_hmi_handler_nb = {
+ .notifier_call = opal_handle_hmi_event,
+ .next = NULL,
+ .priority = 0,
+};
+
+static int __init opal_hmi_handler_init(void)
+{
+ int ret;
+
+ if (!opal_hmi_handler_nb_init) {
+ ret = opal_message_notifier_register(
+ OPAL_MSG_HMI_EVT, &opal_hmi_handler_nb);
+ if (ret) {
+ pr_err("%s: Can't register OPAL event notifier (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ opal_hmi_handler_nb_init = 1;
+ }
+ return 0;
+}
+subsys_initcall(opal_hmi_handler_init);
diff --git a/arch/powerpc/platforms/powernv/opal-wrappers.S b/arch/powerpc/platforms/powernv/opal-wrappers.S
index 4abbff2..8d98745 100644
--- a/arch/powerpc/platforms/powernv/opal-wrappers.S
+++ b/arch/powerpc/platforms/powernv/opal-wrappers.S
@@ -146,3 +146,4 @@ OPAL_CALL(opal_sync_host_reboot, OPAL_SYNC_HOST_REBOOT);
OPAL_CALL(opal_sensor_read, OPAL_SENSOR_READ);
OPAL_CALL(opal_get_param, OPAL_GET_PARAM);
OPAL_CALL(opal_set_param, OPAL_SET_PARAM);
+OPAL_CALL(opal_handle_hmi, OPAL_HANDLE_HMI);
diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
index af4c5ac..05b24dc 100644
--- a/arch/powerpc/platforms/powernv/opal.c
+++ b/arch/powerpc/platforms/powernv/opal.c
@@ -192,9 +192,6 @@ static int __init opal_register_exception_handlers(void)
* fwnmi area at 0x7000 to provide the glue space to OPAL
*/
glue = 0x7000;
- opal_register_exception_handler(OPAL_HYPERVISOR_MAINTENANCE_HANDLER,
- 0, glue);
- glue += 128;
opal_register_exception_handler(OPAL_SOFTPATCH_HANDLER, 0, glue);
#endif
@@ -516,15 +513,41 @@ int opal_machine_check(struct pt_regs *regs)
/* Early hmi handler called in real mode. */
int opal_hmi_exception_early(struct pt_regs *regs)
{
- /* TODO: Call opal hmi handler. */
+ s64 rc;
+
+ /*
+ * call opal hmi handler. Pass paca address as token.
+ * The return value OPAL_SUCCESS is an indication that there is
+ * an HMI event generated waiting to pull by Linux.
+ */
+ rc = opal_handle_hmi();
+ if (rc == OPAL_SUCCESS) {
+ local_paca->hmi_event_available = 1;
+ return 1;
+ }
return 0;
}
/* HMI exception handler called in virtual mode during check_irq_replay. */
int opal_handle_hmi_exception(struct pt_regs *regs)
{
- /* TODO: Retrive and print HMI event from OPAL. */
- return 0;
+ s64 rc;
+ __be64 evt = 0;
+
+ /*
+ * Check if HMI event is available.
+ * if Yes, then call opal_poll_events to pull opal messages and
+ * process them.
+ */
+ if (!local_paca->hmi_event_available)
+ return 0;
+
+ local_paca->hmi_event_available = 0;
+ rc = opal_poll_events(&evt);
+ if (rc == OPAL_SUCCESS && evt)
+ opal_do_notifier(be64_to_cpu(evt));
+
+ return 1;
}
static uint64_t find_recovery_address(uint64_t nip)
^ permalink raw reply related
* [PATCH 3/3] powerpc/book3s: handle HMIs for cpus in nap mode.
From: Mahesh J Salgaonkar @ 2014-07-29 13:10 UTC (permalink / raw)
To: linuxppc-dev, Benjamin Herrenschmidt
In-Reply-To: <20140729130900.13492.85976.stgit@mars>
From: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
HMIs are thread specific and can come while thread is in sleep/nap mode.
Hence with SMT=off mode we can receive HMIs on sleeping threads. For
interrupt received in nap mode, cpu wakes up at system reset vector, clears
the interrupt and go back to nap mode again. But HMIs are sticky and they
keep happening until we clear reason bits from HMER. Hence add a special
check for HMI in reset vector (through power7_wakeup_* functions) and
invoke opal call to handle HMI.
Signed-off-by: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
---
arch/powerpc/kernel/idle_power7.S | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/arch/powerpc/kernel/idle_power7.S b/arch/powerpc/kernel/idle_power7.S
index 5cf3d36..06305f6 100644
--- a/arch/powerpc/kernel/idle_power7.S
+++ b/arch/powerpc/kernel/idle_power7.S
@@ -135,10 +135,36 @@ _GLOBAL(power7_sleep)
b power7_powersave_common
/* No return */
+#define CHECK_HMI_INTERRUPT \
+ mfspr r0,SPRN_SRR1; \
+BEGIN_FTR_SECTION_NESTED(66); \
+ rlwinm r0,r0,45-31,0xf; /* extract wake reason field (P8) */ \
+FTR_SECTION_ELSE_NESTED(66); \
+ rlwinm r0,r0,45-31,0xe; /* P7 wake reason field is 3 bits */ \
+ALT_FTR_SECTION_END_NESTED_IFSET(CPU_FTR_ARCH_207S, 66); \
+ cmpwi r0,0xa; /* Hypervisor maintenance ? */ \
+ bne 20f; \
+ /* Invoke opal call to handle hmi */ \
+ ld r2,PACATOC(r13); \
+ ld r1,PACAR1(r13); \
+ std r3,ORIG_GPR3(r1); /* Save original r3 */ \
+ li r0,OPAL_HANDLE_HMI; \
+ LOAD_REG_ADDR(r11,opal); \
+ ld r12,8(r11); \
+ ld r2,0(r11); \
+ mtctr r12; \
+ bctrl; \
+ ld r3,ORIG_GPR3(r1); /* Restore original r3 */ \
+20: nop;
+
+
_GLOBAL(power7_wakeup_tb_loss)
ld r2,PACATOC(r13);
ld r1,PACAR1(r13)
+BEGIN_FTR_SECTION
+ CHECK_HMI_INTERRUPT
+END_FTR_SECTION_IFSET(CPU_FTR_HVMODE)
/* Time base re-sync */
li r0,OPAL_RESYNC_TIMEBASE
LOAD_REG_ADDR(r11,opal);
@@ -163,6 +189,9 @@ _GLOBAL(power7_wakeup_tb_loss)
_GLOBAL(power7_wakeup_loss)
ld r1,PACAR1(r13)
+BEGIN_FTR_SECTION
+ CHECK_HMI_INTERRUPT
+END_FTR_SECTION_IFSET(CPU_FTR_HVMODE)
REST_NVGPRS(r1)
REST_GPR(2, r1)
ld r3,_CCR(r1)
@@ -178,6 +207,9 @@ _GLOBAL(power7_wakeup_noloss)
lbz r0,PACA_NAPSTATELOST(r13)
cmpwi r0,0
bne power7_wakeup_loss
+BEGIN_FTR_SECTION
+ CHECK_HMI_INTERRUPT
+END_FTR_SECTION_IFSET(CPU_FTR_HVMODE)
ld r1,PACAR1(r13)
ld r4,_MSR(r1)
ld r5,_NIP(r1)
^ permalink raw reply related
* [patch added to the 3.12 stable tree] locking/mutex: Disable optimistic spinning on some architectures
From: Jiri Slaby @ 2014-07-29 15:09 UTC (permalink / raw)
To: stable
Cc: Peter Zijlstra, Catalin Marinas, Will Deacon, James Bottomley,
Davidlohr Bueso, sparclinux, Jiri Slaby, Ingo Molnar,
Russell King, James E.J. Bottomley, Linus Torvalds, Paul McKenney,
James Hogan, Chris Metcalf, John David Anglin, linux-arm-kernel,
Jason Low, Waiman Long, Vineet Gupta, linux-kernel, linuxppc-dev,
David Miller
In-Reply-To: <1406646576-1299-1-git-send-email-jslaby@suse.cz>
From: Peter Zijlstra <peterz@infradead.org>
This patch has been added to the 3.12 stable tree. If you have any
objections, please let us know.
===============
commit 4badad352a6bb202ec68afa7a574c0bb961e5ebc upstream.
The optimistic spin code assumes regular stores and cmpxchg() play nice;
this is found to not be true for at least: parisc, sparc32, tile32,
metag-lock1, arc-!llsc and hexagon.
There is further wreckage, but this in particular seemed easy to
trigger, so blacklist this.
Opt in for known good archs.
Signed-off-by: Peter Zijlstra <peterz@infradead.org>
Reported-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: David Miller <davem@davemloft.net>
Cc: Chris Metcalf <cmetcalf@tilera.com>
Cc: James Bottomley <James.Bottomley@hansenpartnership.com>
Cc: Vineet Gupta <vgupta@synopsys.com>
Cc: Jason Low <jason.low2@hp.com>
Cc: Waiman Long <waiman.long@hp.com>
Cc: "James E.J. Bottomley" <jejb@parisc-linux.org>
Cc: Paul McKenney <paulmck@linux.vnet.ibm.com>
Cc: John David Anglin <dave.anglin@bell.net>
Cc: James Hogan <james.hogan@imgtec.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Davidlohr Bueso <davidlohr@hp.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Russell King <linux@arm.linux.org.uk>
Cc: Will Deacon <will.deacon@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linuxppc-dev@lists.ozlabs.org
Cc: sparclinux@vger.kernel.org
Link: http://lkml.kernel.org/r/20140606175316.GV13930@laptop.programming.kicks-ass.net
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
---
arch/arm/Kconfig | 1 +
arch/arm64/Kconfig | 1 +
arch/powerpc/Kconfig | 1 +
arch/sparc/Kconfig | 1 +
arch/x86/Kconfig | 1 +
kernel/Kconfig.locks | 5 ++++-
6 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index e47fcd1e9645..99e1ce978cf9 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -5,6 +5,7 @@ config ARM
select ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE
select ARCH_HAS_TICK_BROADCAST if GENERIC_CLOCKEVENTS_BROADCAST
select ARCH_HAVE_CUSTOM_GPIO_H
+ select ARCH_SUPPORTS_ATOMIC_RMW
select ARCH_WANT_IPC_PARSE_VERSION
select BUILDTIME_EXTABLE_SORT if MMU
select CLONE_BACKWARDS
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index c04454876bcb..fe70eaea0e28 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1,6 +1,7 @@
config ARM64
def_bool y
select ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE
+ select ARCH_SUPPORTS_ATOMIC_RMW
select ARCH_WANT_OPTIONAL_GPIOLIB
select ARCH_WANT_COMPAT_IPC_PARSE_VERSION
select ARCH_WANT_FRAME_POINTERS
diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index d5d026b6d237..2e0ddfadc0b9 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -138,6 +138,7 @@ config PPC
select OLD_SIGSUSPEND
select OLD_SIGACTION if PPC32
select HAVE_DEBUG_STACKOVERFLOW
+ select ARCH_SUPPORTS_ATOMIC_RMW
config EARLY_PRINTK
bool
diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig
index 4e5683877b93..d60f34dbae89 100644
--- a/arch/sparc/Kconfig
+++ b/arch/sparc/Kconfig
@@ -75,6 +75,7 @@ config SPARC64
select ARCH_HAVE_NMI_SAFE_CMPXCHG
select HAVE_C_RECORDMCOUNT
select NO_BOOTMEM
+ select ARCH_SUPPORTS_ATOMIC_RMW
config ARCH_DEFCONFIG
string
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index eb2dfa61eabe..9dc1a24d41b8 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -123,6 +123,7 @@ config X86
select COMPAT_OLD_SIGACTION if IA32_EMULATION
select RTC_LIB
select HAVE_DEBUG_STACKOVERFLOW
+ select ARCH_SUPPORTS_ATOMIC_RMW
config INSTRUCTION_DECODER
def_bool y
diff --git a/kernel/Kconfig.locks b/kernel/Kconfig.locks
index d2b32ac27a39..ecee67a00f5f 100644
--- a/kernel/Kconfig.locks
+++ b/kernel/Kconfig.locks
@@ -220,6 +220,9 @@ config INLINE_WRITE_UNLOCK_IRQRESTORE
endif
+config ARCH_SUPPORTS_ATOMIC_RMW
+ bool
+
config MUTEX_SPIN_ON_OWNER
def_bool y
- depends on SMP && !DEBUG_MUTEXES
+ depends on SMP && !DEBUG_MUTEXES && ARCH_SUPPORTS_ATOMIC_RMW
--
2.0.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox