* Re: [PATCH v6 04/46] percpu_rwlock: Implement the core design of Per-CPU Reader-Writer Locks
From: Lai Jiangshan @ 2013-03-05 16:25 UTC (permalink / raw)
To: Srivatsa S. Bhat
Cc: Lai Jiangshan, linux-doc, peterz, fweisbec, oleg,
Michel Lespinasse, mingo, linux-arch, linux, xiaoguangrong,
wangyun, paulmck, nikunj, linux-pm, rusty, rostedt, rjw, namhyung,
tglx, linux-arm-kernel, netdev, linux-kernel, vincent.guittot,
sbw, tj, akpm, linuxppc-dev
In-Reply-To: <513105DD.8010908@linux.vnet.ibm.com>
On 02/03/13 03:47, Srivatsa S. Bhat wrote:
> On 03/01/2013 11:20 PM, Lai Jiangshan wrote:
>> On 28/02/13 05:19, Srivatsa S. Bhat wrote:
>>> On 02/27/2013 06:03 AM, Lai Jiangshan wrote:
>>>> On Wed, Feb 27, 2013 at 3:30 AM, Srivatsa S. Bhat
>>>> <srivatsa.bhat@linux.vnet.ibm.com> wrote:
>>>>> On 02/26/2013 09:55 PM, Lai Jiangshan wrote:
>>>>>> On Tue, Feb 26, 2013 at 10:22 PM, Srivatsa S. Bhat
>>>>>> <srivatsa.bhat@linux.vnet.ibm.com> wrote:
>>>>>>>
>>>>>>> Hi Lai,
>>>>>>>
>>>>>>> I'm really not convinced that piggy-backing on lglocks would help
>>>>>>> us in any way. But still, let me try to address some of the points
>>>>>>> you raised...
>>>>>>>
>>>>>>> On 02/26/2013 06:29 PM, Lai Jiangshan wrote:
>>>>>>>> On Tue, Feb 26, 2013 at 5:02 PM, Srivatsa S. Bhat
>>>>>>>> <srivatsa.bhat@linux.vnet.ibm.com> wrote:
>>>>>>>>> On 02/26/2013 05:47 AM, Lai Jiangshan wrote:
>>>>>>>>>> On Tue, Feb 26, 2013 at 3:26 AM, Srivatsa S. Bhat
>>>>>>>>>> <srivatsa.bhat@linux.vnet.ibm.com> wrote:
>>>>>>>>>>> Hi Lai,
>>>>>>>>>>>
>>>>>>>>>>> On 02/25/2013 09:23 PM, Lai Jiangshan wrote:
>>>>>>>>>>>> Hi, Srivatsa,
>>>>>>>>>>>>
>>>>>>>>>>>> The target of the whole patchset is nice for me.
>>>>>>>>>>>
>>>>>>>>>>> Cool! Thanks :-)
>>>>>>>>>>>
>>>>>>>>> [...]
>>>>>>>>>
>>>>>>>>> Unfortunately, I see quite a few issues with the code above. IIUC, the
>>>>>>>>> writer and the reader both increment the same counters. So how will the
>>>>>>>>> unlock() code in the reader path know when to unlock which of the locks?
>>>>>>>>
>>>>>>>> The same as your code, the reader(which nested in write C.S.) just dec
>>>>>>>> the counters.
>>>>>>>
>>>>>>> And that works fine in my case because the writer and the reader update
>>>>>>> _two_ _different_ counters.
>>>>>>
>>>>>> I can't find any magic in your code, they are the same counter.
>>>>>>
>>>>>> /*
>>>>>> * It is desirable to allow the writer to acquire the percpu-rwlock
>>>>>> * for read (if necessary), without deadlocking or getting complaints
>>>>>> * from lockdep. To achieve that, just increment the reader_refcnt of
>>>>>> * this CPU - that way, any attempt by the writer to acquire the
>>>>>> * percpu-rwlock for read, will get treated as a case of nested percpu
>>>>>> * reader, which is safe, from a locking perspective.
>>>>>> */
>>>>>> this_cpu_inc(pcpu_rwlock->rw_state->reader_refcnt);
>>>>>>
>>>>>
>>>>> Whoa! Hold on, were you really referring to _this_ increment when you said
>>>>> that, in your patch you would increment the refcnt at the writer? Then I guess
>>>>> there is a major disconnect in our conversations. (I had assumed that you were
>>>>> referring to the update of writer_signal, and were just trying to have a single
>>>>> refcnt instead of reader_refcnt and writer_signal).
>>>>
>>>> https://github.com/laijs/linux/commit/53e5053d5b724bea7c538b11743d0f420d98f38d
>>>>
>>>> Sorry the name "fallback_reader_refcnt" misled you.
>>>>
>>> [...]
>>>
>>>>>> All I was considered is "nested reader is seldom", so I always
>>>>>> fallback to rwlock when nested.
>>>>>> If you like, I can add 6 lines of code, the overhead is
>>>>>> 1 spin_try_lock()(fast path) + N __this_cpu_inc()
>>>>>>
>>>>>
>>>>> I'm assuming that calculation is no longer valid, considering that
>>>>> we just discussed how the per-cpu refcnt that you were using is quite
>>>>> unnecessary and can be removed.
>>>>>
>>>>> IIUC, the overhead with your code, as per above discussion would be:
>>>>> 1 spin_try_lock() [non-nested] + N read_lock(global_rwlock).
>>>>
>>>> https://github.com/laijs/linux/commit/46334544bb7961550b7065e015da76f6dab21f16
>>>>
>>>> Again, I'm so sorry the name "fallback_reader_refcnt" misled you.
>>>>
>>>
>>> At this juncture I really have to admit that I don't understand your
>>> intentions at all. What are you really trying to prove? Without giving
>>> a single good reason why my code is inferior, why are you even bringing
>>> up the discussion about a complete rewrite of the synchronization code?
>>> http://article.gmane.org/gmane.linux.kernel.cross-arch/17103
>>> http://article.gmane.org/gmane.linux.power-management.general/31345
>>>
>>> I'm beginning to add 2 + 2 together based on the kinds of questions you
>>> have been asking...
>>>
>>> You posted a patch in this thread and started a discussion around it without
>>> even establishing a strong reason to do so. Now you point me to your git
>>> tree where your patches have even more traces of ideas being borrowed from
>>> my patchset (apart from my own ideas/code, there are traces of others' ideas
>>> being borrowed too - for example, it was Oleg who originally proposed the
>>> idea of splitting up the counter into 2 parts and I'm seeing that it is
>>> slowly crawling into your code with no sign of appropriate credits).
>>> http://article.gmane.org/gmane.linux.network/260288
>>>
>>> And in reply to my mail pointing out the performance implications of the
>>> global read_lock at the reader side in your code, you said you'll come up
>>> with a comparison between that and my patchset.
>>> http://article.gmane.org/gmane.linux.network/260288
>>> The issue has been well-documented in my patch description of patch 4.
>>> http://article.gmane.org/gmane.linux.kernel/1443258
>>>
>>> Are you really trying to pit bits and pieces of my own ideas/versions
>>> against one another and claiming them as your own?
>>>
>>> You projected the work involved in handling the locking issues pertaining
>>> to CPU_DYING notifiers etc as a TODO, despite the fact that I had explicitly
>>> noted in my cover letter that I had audited and taken care of all of them.
>>> http://article.gmane.org/gmane.linux.documentation/9727
>>> http://article.gmane.org/gmane.linux.documentation/9520
>>>
>>> You failed to acknowledge (on purpose?) that I had done a tree-wide
>>> conversion despite the fact that you were replying to the very thread which
>>> had the 46 patches which did exactly that (and I had also mentioned it
>>> explicitly in my cover letter).
>>> http://article.gmane.org/gmane.linux.documentation/9727
>>> http://article.gmane.org/gmane.linux.documentation/9520
>>>
>>> You then started probing more and more about the technique I used to do
>>> the tree-wide conversion.
>>> http://article.gmane.org/gmane.linux.kernel.cross-arch/17111
>>>
>>> You also retorted saying you did go through my patch descriptions, so
>>> its not like you have missed reading them.
>>> http://article.gmane.org/gmane.linux.power-management.general/31345
>>>
>>> Each of these when considered individually, might appear like innocuous and
>>> honest attempts at evaluating my code. But when put together, I'm beginning
>>> to sense a whole different angle to it altogether, as if you are trying
>>> to spin your own patch series, complete with the locking framework _and_
>>> the tree-wide conversion, heavily borrowed from mine. At the beginning of
>>> this discussion, I predicted that the lglock version that you are proposing
>>> would end up being either less efficient than my version or look very similar
>>> to my version. http://article.gmane.org/gmane.linux.kernel/1447139
>>>
>>> I thought it was just the former till now, but its not hard to see how it
>>> is getting closer to becoming the latter too. So yeah, I'm not amused.
>>>
>>> Maybe (and hopefully) you are just trying out different ideas on your own,
>>> and I'm just being paranoid. I really hope that is the case. If you are just
>>> trying to review my code, then please stop sending patches with borrowed ideas
>>> with your sole Signed-off-by, and purposefully ignoring the work already done
>>> in my patchset, because it is really starting to look suspicious, at least
>>> to me.
>>>
>>> Don't get me wrong - I'll whole-heartedly acknowledge and appreciate if
>>> _your_ code is better than mine. I just don't like the idea of somebody
>>> plagiarizing my ideas/code (or even others' ideas for that matter).
>>> However, I sincerely apologize in advance if I misunderstood/misjudged your
>>> intentions; I just wanted to voice my concerns out loud at this point,
>>> considering the bad feeling I got by looking at your responses collectively.
>>>
>>
>> Hi, Srivatsa
>>
>> I'm sorry, big apology to you.
>> I'm bad in communication and I did be wrong.
>> I tended to improve the codes but in false direction.
>>
>
> OK, in that case, I'm extremely sorry too, for jumping on you like that.
> I hope you'll forgive me for the uneasiness it caused.
>
> Now that I understand that you were simply trying to help, I would like to
> express my gratitude for your time, effort and inputs in improving the design
> of the stop-machine replacement.
>
> I'm looking forward to working with you on this as well as future endeavours,
> so I sincerely hope that we can put this unfortunate incident behind us and
> collaborate effectively with renewed mutual trust and good-will.
>
> Thank you very much!
>
Hi, Srivatsa,
I'm sorry again, I delayed your works.
I have some thinkings about the way how to get this work done.
First step: (2~3 patches)
Use preempt_disable() to implement get_online_cpu_atomic(), and add lockdep for it.
Second step:
Conversion patches.
We can send the patchset of the above steps at first.
{
It does not change any behavior of the kernel.
and it is annotation(instead of direct preempt_diable() without comments sometimes),
so I expected they can be merged very early.
}
Third step:
After all people confide the conversion patches covered all cases and cpuhotplug site is ready for it,
we will implement get_online_cpu_atomic() via locks and remove stop_machine() from cpuhotplug.
Any thought?
Thanks,
Lai
If I have time, I will help you for the patches of the first step.
(I was assigned bad job in office-time, I can only do kernel-dev work in night.)
And for step2, I will write a checklist or spatch-script.
^ permalink raw reply
* Re: [PATCH V2] lglock: add read-preference local-global rwlock
From: Michel Lespinasse @ 2013-03-05 16:19 UTC (permalink / raw)
To: Lai Jiangshan
Cc: Lai Jiangshan, linux-doc, peterz, fweisbec, linux-kernel, mingo,
linux-arch, linux, xiaoguangrong, wangyun, paulmck, nikunj,
linux-pm, rusty, rostedt, rjw, namhyung, tglx, linux-arm-kernel,
netdev, Oleg Nesterov, vincent.guittot, sbw, Srivatsa S. Bhat, tj,
akpm, linuxppc-dev
In-Reply-To: <51360ED1.3030104@cn.fujitsu.com>
Hi Lai,
Just a few comments about your v2 proposal. Hopefully you'll catch
these before you send out v3 :)
- I would prefer reader_refcnt to be unsigned int instead of unsigned long
- I would like some comment to indicate that lgrwlocks don't have
reader-writer fairness and are thus somewhat discouraged
(people could use plain lglock if they don't need reader preference,
though even that use (as brlock) is discouraged already :)
- I don't think FALLBACK_BASE is necessary (you already mentioned you'd
drop it)
- I prefer using the fallback_rwlock's dep_map for lockdep tracking.
I feel this is more natural since we want the lgrwlock to behave as
the rwlock, not as the lglock.
- I prefer to avoid return statements in the middle of functions when
it's easyto do so.
Attached is my current version (based on an earlier version of your code).
You don't have to take it as is but I feel it makes for a more concrete
suggestion :)
Thanks,
----------------------------8<-------------------------------------------
lglock: add read-preference lgrwlock
Current lglock may be used as a fair rwlock; however sometimes a
read-preference rwlock is preferred. One such use case recently came
up for get_cpu_online_atomic().
This change adds a new lgrwlock with the following properties:
- high performance read side, using only cpu-local structures when there
is no write side to contend with;
- correctness guarantees similar to rwlock_t: recursive readers are allowed
and the lock's read side is not ordered vs other locks;
- low performance write side (comparable to lglocks' global side).
The implementation relies on the following principles:
- reader_refcnt is a local lock count; it indicates how many recursive
read locks are taken using the local lglock;
- lglock is used by readers for local locking; it must be acquired
before reader_refcnt becomes nonzero and released after reader_refcnt
goes back to zero;
- fallback_rwlock is used by readers for global locking; it is acquired
when fallback_reader_refcnt is zero and the trylock fails on lglock.
- writers take both the lglock write side and the fallback_rwlock, thus
making sure to exclude both local and global readers.
Thanks to Srivatsa S. Bhat for proposing a lock with these requirements
and Lai Jiangshan for proposing this algorithm as an lglock extension.
Signed-off-by: Michel Lespinasse <walken@google.com>
---
include/linux/lglock.h | 46 +++++++++++++++++++++++++++++++++++++++
kernel/lglock.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 104 insertions(+)
diff --git a/include/linux/lglock.h b/include/linux/lglock.h
index 0d24e932db0b..8b59084935d5 100644
--- a/include/linux/lglock.h
+++ b/include/linux/lglock.h
@@ -67,4 +67,50 @@ void lg_local_unlock_cpu(struct lglock *lg, int cpu);
void lg_global_lock(struct lglock *lg);
void lg_global_unlock(struct lglock *lg);
+/*
+ * lglock may be used as a read write spinlock if desired (though this is
+ * not encouraged as the write side scales badly on high CPU count machines).
+ * It has reader/writer fairness when used that way.
+ *
+ * However, sometimes it is desired to have an unfair rwlock instead, with
+ * reentrant readers that don't need to be ordered vs other locks, comparable
+ * to rwlock_t. lgrwlock implements such semantics.
+ */
+struct lgrwlock {
+ unsigned int __percpu *reader_refcnt;
+ struct lglock lglock;
+ rwlock_t fallback_rwlock;
+};
+
+#define __DEFINE_LGRWLOCK_PERCPU_DATA(name) \
+ static DEFINE_PER_CPU(unsigned int, name ## _refcnt); \
+ static DEFINE_PER_CPU(arch_spinlock_t, name ## _lock) \
+ = __ARCH_SPIN_LOCK_UNLOCKED;
+
+#define __LGRWLOCK_INIT(name) { \
+ .reader_refcnt = &name ## _refcnt, \
+ .lglock = { .lock = &name ## _lock }, \
+ .fallback_rwlock = __RW_LOCK_UNLOCKED(name.fallback_rwlock) \
+}
+
+#define DEFINE_LGRWLOCK(name) \
+ __DEFINE_LGRWLOCK_PERCPU_DATA(name) \
+ struct lgrwlock name = __LGRWLOCK_INIT(name)
+
+#define DEFINE_STATIC_LGRWLOCK(name) \
+ __DEFINE_LGRWLOCK_PERCPU_DATA(name) \
+ static struct lgrwlock name = __LGRWLOCK_INIT(name)
+
+static inline void lg_rwlock_init(struct lgrwlock *lgrw, char *name)
+{
+ lg_lock_init(&lgrw->lglock, name);
+}
+
+void lg_read_lock(struct lgrwlock *lgrw);
+void lg_read_unlock(struct lgrwlock *lgrw);
+void lg_write_lock(struct lgrwlock *lgrw);
+void lg_write_unlock(struct lgrwlock *lgrw);
+void __lg_read_write_lock(struct lgrwlock *lgrw);
+void __lg_read_write_unlock(struct lgrwlock *lgrw);
+
#endif
diff --git a/kernel/lglock.c b/kernel/lglock.c
index 86ae2aebf004..e78a7c95dbfd 100644
--- a/kernel/lglock.c
+++ b/kernel/lglock.c
@@ -87,3 +87,61 @@ void lg_global_unlock(struct lglock *lg)
preempt_enable();
}
EXPORT_SYMBOL(lg_global_unlock);
+
+void lg_read_lock(struct lgrwlock *lgrw)
+{
+ preempt_disable();
+
+ if (__this_cpu_read(*lgrw->reader_refcnt) ||
+ arch_spin_trylock(this_cpu_ptr(lgrw->lglock.lock))) {
+ __this_cpu_inc(*lgrw->reader_refcnt);
+ rwlock_acquire_read(&lgrw->fallback_rwlock.dep_map,
+ 0, 0, _RET_IP_);
+ } else {
+ read_lock(&lgrw->fallback_rwlock);
+ }
+}
+EXPORT_SYMBOL(lg_read_lock);
+
+void lg_read_unlock(struct lgrwlock *lgrw)
+{
+ if (likely(__this_cpu_read(*lgrw->reader_refcnt))) {
+ rwlock_release(&lgrw->fallback_rwlock.dep_map,
+ 1, _RET_IP_);
+ if (!__this_cpu_dec_return(*lgrw->reader_refcnt))
+ arch_spin_unlock(this_cpu_ptr(lgrw->lglock.lock));
+ } else {
+ read_unlock(&lgrw->fallback_rwlock);
+ }
+
+ preempt_enable();
+}
+EXPORT_SYMBOL(lg_read_unlock);
+
+void lg_write_lock(struct lgrwlock *lgrw)
+{
+ lg_global_lock(&lgrw->lglock);
+ write_lock(&lgrw->fallback_rwlock);
+}
+EXPORT_SYMBOL(lg_write_lock);
+
+void lg_write_unlock(struct lgrwlock *lgrw)
+{
+ write_unlock(&lgrw->fallback_rwlock);
+ lg_global_unlock(&lgrw->lglock);
+}
+EXPORT_SYMBOL(lg_write_unlock);
+
+void __lg_read_write_lock(struct lgrwlock *lgrw)
+{
+ lg_write_lock(lgrw);
+ __this_cpu_write(*lgrw->reader_refcnt, 1);
+}
+EXPORT_SYMBOL(__lg_read_write_lock);
+
+void __lg_read_write_unlock(struct lgrwlock *lgrw)
+{
+ __this_cpu_write(*lgrw->reader_refcnt, 0);
+ lg_write_unlock(lgrw);
+}
+EXPORT_SYMBOL(__lg_read_write_unlock);
--
Michel "Walken" Lespinasse
A program is never fully debugged until the last user dies.
^ permalink raw reply related
* [PATCH] powerpc/85xx: Move ePAPR paravirt initialization earlier
From: Laurentiu Tudor @ 2013-03-05 15:52 UTC (permalink / raw)
To: linuxppc-dev; +Cc: Laurentiu Tudor
From: Tudor Laurentiu <laurentiu.tudor@freescale.com>
The ePAPR para-virtualization needs to happen very early
otherwise the bytechannel based console will silently
drop some of the early boot messages.
Before this patch, this is how the kernel log started:
-----------------------------------------------------
> Brought up 2 CPUs
> devtmpfs: initialized
> NET: Registered protocol family 16
[...]
-----------------------------------------------------
After the patch the early messages show up:
-----------------------------------------------------
> Using P5020 DS machine description
> MMU: Supported page sizes
> 4 KB as direct
> 4096 KB as direct
[...]
-----------------------------------------------------
At console init, the kernel tried to flush the log buffer.
Since the paravirt was not yet initialized the console write
function failed silently, thus losing the buffered messages.
Signed-off-by: Laurentiu Tudor <Laurentiu.Tudor@freescale.com>
---
arch/powerpc/include/asm/epapr_hcalls.h | 6 ++++++
arch/powerpc/kernel/epapr_paravirt.c | 3 +--
arch/powerpc/kernel/setup_32.c | 2 ++
arch/powerpc/kernel/setup_64.c | 3 +++
4 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/arch/powerpc/include/asm/epapr_hcalls.h b/arch/powerpc/include/asm/epapr_hcalls.h
index d3d6342..544176e 100644
--- a/arch/powerpc/include/asm/epapr_hcalls.h
+++ b/arch/powerpc/include/asm/epapr_hcalls.h
@@ -105,6 +105,12 @@
extern bool epapr_paravirt_enabled;
extern u32 epapr_hypercall_start[];
+#ifdef CONFIG_EPAPR_PARAVIRT
+int __init epapr_paravirt_init(void);
+#else
+static inline int epapr_paravirt_init(void) { return 0; }
+#endif
+
/*
* We use "uintptr_t" to define a register because it's guaranteed to be a
* 32-bit integer on a 32-bit platform, and a 64-bit integer on a 64-bit
diff --git a/arch/powerpc/kernel/epapr_paravirt.c b/arch/powerpc/kernel/epapr_paravirt.c
index f3eab85..9848713 100644
--- a/arch/powerpc/kernel/epapr_paravirt.c
+++ b/arch/powerpc/kernel/epapr_paravirt.c
@@ -28,7 +28,7 @@ extern u32 epapr_ev_idle_start[];
bool epapr_paravirt_enabled;
-static int __init epapr_paravirt_init(void)
+int __init epapr_paravirt_init(void)
{
struct device_node *hyper_node;
const u32 *insts;
@@ -58,4 +58,3 @@ static int __init epapr_paravirt_init(void)
return 0;
}
-early_initcall(epapr_paravirt_init);
diff --git a/arch/powerpc/kernel/setup_32.c b/arch/powerpc/kernel/setup_32.c
index a8f54ec..1464655 100644
--- a/arch/powerpc/kernel/setup_32.c
+++ b/arch/powerpc/kernel/setup_32.c
@@ -38,6 +38,7 @@
#include <asm/serial.h>
#include <asm/udbg.h>
#include <asm/mmu_context.h>
+#include <asm/epapr_hcalls.h>
#include "setup.h"
@@ -327,4 +328,5 @@ void __init setup_arch(char **cmdline_p)
/* Initialize the MMU context management stuff */
mmu_context_init();
+ epapr_paravirt_init();
}
diff --git a/arch/powerpc/kernel/setup_64.c b/arch/powerpc/kernel/setup_64.c
index 6da881b..ce092ac 100644
--- a/arch/powerpc/kernel/setup_64.c
+++ b/arch/powerpc/kernel/setup_64.c
@@ -66,6 +66,7 @@
#include <asm/code-patching.h>
#include <asm/kvm_ppc.h>
#include <asm/hugetlb.h>
+#include <asm/epapr_hcalls.h>
#include "setup.h"
@@ -599,6 +600,8 @@ void __init setup_arch(char **cmdline_p)
/* Initialize the MMU context management stuff */
mmu_context_init();
+ epapr_paravirt_init();
+
kvm_linear_init();
/* Interrupt code needs to be 64K-aligned */
--
1.7.6.5
^ permalink raw reply related
* [PATCH] powerpc/watchdog: Don't enable interrupt on PPC64 BookE
From: Laurentiu Tudor @ 2013-03-05 15:52 UTC (permalink / raw)
To: linuxppc-dev; +Cc: Laurentiu Tudor
From: Tudor Laurentiu <laurentiu.tudor@freescale.com>
Critical interrupts are not handled on PPC64 BookE machines,
so when the first watchdog interrupt fires the machine will
freeze without a warning until it's rebooted by the second
watchdog trigger.
Plus, the interrupt isn't used anyway since the driver
expects a usermode app to ping the watchdog periodically.
Signed-off-by: Laurentiu Tudor <Laurentiu.Tudor@freescale.com>
---
drivers/watchdog/booke_wdt.c | 8 ++++++++
1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/drivers/watchdog/booke_wdt.c b/drivers/watchdog/booke_wdt.c
index c0bc92d..5eb2040 100644
--- a/drivers/watchdog/booke_wdt.c
+++ b/drivers/watchdog/booke_wdt.c
@@ -122,6 +122,14 @@ static void __booke_wdt_enable(void *data)
val &= ~WDTP_MASK;
val |= (TCR_WIE|TCR_WRC(WRC_CHIP)|WDTP(booke_wdt_period));
+#ifdef CONFIG_PPC_BOOK3E_64
+ /*
+ * Crit ints are currently broken on PPC64 Book-E, so
+ * just disable them for now.
+ */
+ val &= ~TCR_WIE;
+#endif
+
mtspr(SPRN_TCR, val);
}
--
1.7.6.5
^ permalink raw reply related
* Re: [PATCH V2] lglock: add read-preference local-global rwlock
From: Lai Jiangshan @ 2013-03-05 15:41 UTC (permalink / raw)
To: Srivatsa S. Bhat
Cc: Lai Jiangshan, linux-doc, peterz, fweisbec, linux-kernel,
Michel Lespinasse, mingo, linux-arch, linux, xiaoguangrong,
wangyun, paulmck, nikunj, linux-pm, rusty, rostedt, rjw, namhyung,
tglx, linux-arm-kernel, netdev, Oleg Nesterov, vincent.guittot,
sbw, tj, akpm, linuxppc-dev
In-Reply-To: <513232B6.9060905@linux.vnet.ibm.com>
On 03/03/13 01:11, Srivatsa S. Bhat wrote:
> On 03/02/2013 06:44 PM, Lai Jiangshan wrote:
>> From 345a7a75c314ff567be48983e0892bc69c4452e7 Mon Sep 17 00:00:00 2001
>> From: Lai Jiangshan <laijs@cn.fujitsu.com>
>> Date: Sat, 2 Mar 2013 20:33:14 +0800
>> Subject: [PATCH] lglock: add read-preference local-global rwlock
>>
>> Current lglock is not read-preference, so it can't be used on some cases
>> which read-preference rwlock can do. Example, get_cpu_online_atomic().
>>
> [...]
>> diff --git a/kernel/lglock.c b/kernel/lglock.c
>> index 6535a66..52e9b2c 100644
>> --- a/kernel/lglock.c
>> +++ b/kernel/lglock.c
>> @@ -87,3 +87,71 @@ void lg_global_unlock(struct lglock *lg)
>> preempt_enable();
>> }
>> EXPORT_SYMBOL(lg_global_unlock);
>> +
>> +#define FALLBACK_BASE (1UL << 30)
>> +
>> +void lg_rwlock_local_read_lock(struct lgrwlock *lgrw)
>> +{
>> + struct lglock *lg = &lgrw->lglock;
>> +
>> + preempt_disable();
>> + if (likely(!__this_cpu_read(*lgrw->reader_refcnt))) {
>> + rwlock_acquire_read(&lg->lock_dep_map, 0, 0, _RET_IP_);
>> + if (unlikely(!arch_spin_trylock(this_cpu_ptr(lg->lock)))) {
>> + read_lock(&lgrw->fallback_rwlock);
>> + __this_cpu_write(*lgrw->reader_refcnt, FALLBACK_BASE);
>> + return;
>> + }
>> + }
>> +
>> + __this_cpu_inc(*lgrw->reader_refcnt);
>> +}
>> +EXPORT_SYMBOL(lg_rwlock_local_read_lock);
>> +
>> +void lg_rwlock_local_read_unlock(struct lgrwlock *lgrw)
>> +{
>> + switch (__this_cpu_read(*lgrw->reader_refcnt)) {
>> + case 1:
>> + __this_cpu_write(*lgrw->reader_refcnt, 0);
>> + lg_local_unlock(&lgrw->lglock);
>> + return;
>
> This should be a break, instead of a return, right?
> Otherwise, there will be a preempt imbalance...
"lockdep" and "preempt" are handled in lg_local_unlock(&lgrw->lglock);
Thanks,
Lai
>
>> + case FALLBACK_BASE:
>> + __this_cpu_write(*lgrw->reader_refcnt, 0);
>> + read_unlock(&lgrw->fallback_rwlock);
>> + rwlock_release(&lg->lock_dep_map, 1, _RET_IP_);
>> + break;
>> + default:
>> + __this_cpu_dec(*lgrw->reader_refcnt);
>> + break;
>> + }
>> +
>> + preempt_enable();
>> +}
>
>
> Regards,
> Srivatsa S. Bhat
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
>
^ permalink raw reply
* Re: [PATCH V2] lglock: add read-preference local-global rwlock
From: Lai Jiangshan @ 2013-03-05 15:27 UTC (permalink / raw)
To: Oleg Nesterov
Cc: Lai Jiangshan, linux-doc, peterz, fweisbec, Michel Lespinasse,
mingo, linux-arch, linux, xiaoguangrong, wangyun, paulmck, nikunj,
linux-pm, rusty, rostedt, rjw, namhyung, tglx, linux-arm-kernel,
netdev, linux-kernel, vincent.guittot, sbw, Srivatsa S. Bhat, tj,
akpm, linuxppc-dev
In-Reply-To: <20130302172003.GC29769@redhat.com>
On 03/03/13 01:20, Oleg Nesterov wrote:
> On 03/02, Lai Jiangshan wrote:
>>
>> +void lg_rwlock_local_read_unlock(struct lgrwlock *lgrw)
>> +{
>> + switch (__this_cpu_read(*lgrw->reader_refcnt)) {
>> + case 1:
>> + __this_cpu_write(*lgrw->reader_refcnt, 0);
>> + lg_local_unlock(&lgrw->lglock);
>> + return;
>> + case FALLBACK_BASE:
>> + __this_cpu_write(*lgrw->reader_refcnt, 0);
>> + read_unlock(&lgrw->fallback_rwlock);
>> + rwlock_release(&lg->lock_dep_map, 1, _RET_IP_);
>
> I guess "case 1:" should do rwlock_release() too.
Already do it in "lg_local_unlock(&lgrw->lglock);" before it returns.
(I like reuse old code)
>
> Otherwise, at first glance looks correct...
>
> However, I still think that FALLBACK_BASE only adds the unnecessary
> complications. But even if I am right this is subjective of course, please
> feel free to ignore.
OK, I kill FALLBACK_BASE in later patch.
>
> And btw, I am not sure about lg->lock_dep_map, perhaps we should use
> fallback_rwlock->dep_map ?
Use either one is OK.
>
> We need rwlock_acquire_read() even in the fast-path, and this acquire_read
> should be paired with rwlock_acquire() in _write_lock(), but it does
> spin_acquire(lg->lock_dep_map). Yes, currently this is the same (afaics)
> but perhaps fallback_rwlock->dep_map would be more clean.
>
I can't tell which one is better. I try to use fallback_rwlock->dep_map later.
> Oleg.
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
>
^ permalink raw reply
* [PATCH 4/4][v2] Basic configuration module for Le88266 Zarlink SLIC
From: Sandeep Singh @ 2013-03-05 12:41 UTC (permalink / raw)
To: linuxppc-dev; +Cc: Sandeep Singh, Poonam Aggrwal, linux-arm-kernel
In-Reply-To: <1362487297-19702-1-git-send-email-Sandeep@freescale.com>
- Basic driver which does minimum intialization of the Le88266 SLIC device.
- The code was originally borrowed from Zarlink driver implementation.
- It is not full fledged SLIC driver code, it just does basic
initialization which is required to setup a voice data path between
channels. This is just enough to test and demonstrate TDM functionality
on Freescale platforms using TDM Test Module.
- For full fledged VoIP type of use case proper SLIC driver will be required
which handles all the functionalities of the device.
- Going forward this driver will be replaced by Opoen source Zarlink APIs.
Signed-off-by: Sandeep Singh <Sandeep@freescale.com>
Signed-off-by: Poonam Aggrwal <poonam.aggrwal@freescale.com>
---
drivers/tdm/Kconfig | 1 +
drivers/tdm/Makefile | 2 +-
drivers/tdm/line_ctrl/Kconfig | 12 +
drivers/tdm/line_ctrl/Makefile | 5 +
drivers/tdm/line_ctrl/slic_zarlink.c | 715 ++++++++++++++++++++++++++++++++++
drivers/tdm/line_ctrl/slic_zarlink.h | 131 +++++++
6 files changed, 865 insertions(+), 1 deletions(-)
create mode 100644 drivers/tdm/line_ctrl/Kconfig
create mode 100644 drivers/tdm/line_ctrl/Makefile
create mode 100644 drivers/tdm/line_ctrl/slic_zarlink.c
create mode 100644 drivers/tdm/line_ctrl/slic_zarlink.h
diff --git a/drivers/tdm/Kconfig b/drivers/tdm/Kconfig
index f430adc..434ad6e 100644
--- a/drivers/tdm/Kconfig
+++ b/drivers/tdm/Kconfig
@@ -24,4 +24,5 @@ config TDM_DEBUG_CORE
source drivers/tdm/test/Kconfig
source drivers/tdm/device/Kconfig
+source drivers/tdm/line_ctrl/Kconfig
endif # TDM
diff --git a/drivers/tdm/Makefile b/drivers/tdm/Makefile
index d73fbbd..ea66fff 100644
--- a/drivers/tdm/Makefile
+++ b/drivers/tdm/Makefile
@@ -2,7 +2,7 @@
# Makefile for the TDM core.
#
-obj-$(CONFIG_TDM) += tdm-core.o device/
+obj-$(CONFIG_TDM) += tdm-core.o device/ line_ctrl/
obj-$(CONFIG_TDM_TEST) += test/
ifeq ($(CONFIG_TDM_DEBUG_CORE),y)
EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/tdm/line_ctrl/Kconfig b/drivers/tdm/line_ctrl/Kconfig
new file mode 100644
index 0000000..e7421b9
--- /dev/null
+++ b/drivers/tdm/line_ctrl/Kconfig
@@ -0,0 +1,12 @@
+#
+# TDM line control driver config file
+#
+
+menu "Line Control Devices"
+
+config SLIC_ZARLINK
+ tristate "Zarlink Slic intialization Module"
+ default n
+ ---help---
+ This module initialize and configure the zarlink slic
+endmenu
diff --git a/drivers/tdm/line_ctrl/Makefile b/drivers/tdm/line_ctrl/Makefile
new file mode 100644
index 0000000..91e8916
--- /dev/null
+++ b/drivers/tdm/line_ctrl/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the TDM line control drivers.
+#
+
+obj-y += slic_zarlink.o
diff --git a/drivers/tdm/line_ctrl/slic_zarlink.c b/drivers/tdm/line_ctrl/slic_zarlink.c
new file mode 100644
index 0000000..579d2ab
--- /dev/null
+++ b/drivers/tdm/line_ctrl/slic_zarlink.c
@@ -0,0 +1,715 @@
+/*
+ * drivers/tdm/line_ctrl/slic_zarlink.c
+ *
+ * Copyright (C) 2008-2012 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * SLIC Line Control Module for Zarlink SLICs.
+ * This is a slic control and initialization module.
+ *
+ * Author:Poonam Aggrwal<poonam.aggrwal@freescale.com>
+ * Hemant Agrawal <hemant@freescale.com>
+ * Rajesh Gumasta <rajesh.gumasta@freescale.com>
+ *
+ * Modified by Sandeep Kr Singh <sandeep@freescale.com>
+ * 1. Changed SPI cmnds to restrict transaction length to 1 byte.
+ * 2. Updated probe which now does not relies on modalias.
+ *
+ * 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 driver was created solely by Freescale, without the assistance,
+ * support or intellectual property of Zarlink Semiconductor. No maintenance
+ * or support will be provided by Zarlink Semiconductor regarding this driver
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+ /* Note that this is a complete rewrite of Poonam's slic code.
+ But we have used so much of her original code and ideas that it seems
+ only fair to recognize her as co-author -- Rajesh & Hemant */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/spi/spi.h>
+#include <linux/wait.h>
+#include <linux/param.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include "slic_zarlink.h"
+#include <linux/tdm.h>
+
+#define DRV_DESC "FREESCALE DEVELOPED ZARLINK SLIC DRIVER"
+#define DRV_NAME "legerity"
+
+#define MAX_NUM_OF_SLICS 10
+#define SLIC_TRANS_LEN 1
+#define TDM_PHY_SLIC 1
+#define TDM_PHY_E1 2
+#define TDM_PHY_T1 3
+
+#define TESTING_PRODUCT_CODE
+
+static struct spi_device *g_spi;
+struct spi_transfer t;
+
+struct slic_channel {
+ unsigned int ch1_rx_slot, ch1_tx_slot, ch2_rx_slot, ch2_tx_slot;
+};
+struct slic_channel slic_ch[MAX_NUM_OF_SLICS];
+static int num_slics;
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Poonam Aggrwal<poonam.aggrwal@freescale.com>");
+MODULE_DESCRIPTION(DRV_DESC);
+
+static void
+slic_cmd(struct spi_device *spi, unsigned char channel, unsigned char cmd,
+ unsigned char len, unsigned char *cmdData)
+{
+ unsigned char ecCmd = WRITE_CHANNEL_ENABLE;
+ unsigned char cmdLen;
+
+ /* Write EC command */
+ spi_write(spi, &ecCmd, SLIC_TRANS_LEN);
+
+ /* write EC value */
+ spi_write(spi, &channel, SLIC_TRANS_LEN);
+
+ /* write command */
+ spi_write(spi, &cmd, SLIC_TRANS_LEN);
+
+ /* If read command or write command */
+ if (cmd & 0x01) {
+ for (cmdLen = 0; cmdLen < len; cmdLen++)
+ spi_read(spi, &cmdData[cmdLen], SLIC_TRANS_LEN);
+ }
+ else {
+ for (cmdLen = 0; cmdLen < len; cmdLen++)
+ spi_write(spi, &cmdData[cmdLen], SLIC_TRANS_LEN);
+ }
+}
+
+static void get_slic_product_code(struct spi_device *spi)
+{
+ u8 tx = READ_PRODUCT_CODE;
+ u8 rx = 0x00;
+
+ spi_write(spi, &tx, SLIC_TRANS_LEN);
+ spi_read(spi, &rx, SLIC_TRANS_LEN);
+ printk(KERN_INFO "SLIC: product code 1 read is %x\n", rx);
+
+ spi_read(spi, &rx, SLIC_TRANS_LEN);
+ printk(KERN_INFO "SLIC: product code 2 read is %x\n", rx);
+
+ tx = WRITE_CHANNEL_ENABLE;
+ spi_write(spi, &tx, SLIC_TRANS_LEN);
+ spi_read(spi, &rx, SLIC_TRANS_LEN);
+ printk(KERN_INFO "SLIC: config read is %x\n", rx);
+
+ tx = READ_DEVICE_CONFIGURATION;
+ spi_write(spi, &tx, SLIC_TRANS_LEN);
+ spi_read(spi, &rx, SLIC_TRANS_LEN);
+ printk(KERN_INFO "SLIC: config read is %x\n", rx);
+
+ return;
+}
+
+static int slic_init_configure(struct fsl_tdm_adapt_cfg *tdm_config)
+{
+ char temp1 = 0;
+ char temp2[2];
+ char temp3[3];
+ unsigned char cad[4];
+ unsigned char len;
+ unsigned char channel_id;
+ struct spi_device *spi = g_spi;
+ int slic_id = num_slics;
+
+ temp3[0] = 0x04;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, HW_RESET, len, &temp3[0]);
+
+#ifdef TESTING_PRODUCT_CODE
+ get_slic_product_code(spi);
+#endif
+ temp3[0] = 0x80;
+ switch (tdm_config->tdm_tx_clk) {
+ case 2048000:
+ temp3[0] = temp3[0] | 0x02;
+ break;
+ default:
+ temp3[0] = 0x82;
+ }
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_DEVICE_CFG, len, &temp3[0]);
+
+ temp3[0] = 0x7f;
+ temp3[1] = 0xff;
+ len = 0x02;
+ slic_cmd(spi, CHANNEL1, WRITE_INT_MASK, len, &temp3[0]);
+
+ temp3[0] = 0xff;
+ temp3[1] = 0xff;
+ len = 0x02;
+ slic_cmd(spi, CHANNEL1, WRITE_INT_MASK, len, &temp3[0]);
+
+ temp3[0] = 0x40;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_TXRXCLKSLOT_TXCLKEDGE, len,
+ &temp3[0]);
+
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_SYSTEM_STATE, len, &temp3[0]);
+
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL2, WRITE_SYSTEM_STATE, len, &temp3[0]);
+
+ /* Put the Switching regulators in disabled mode */
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_SWITCH_REGULATOR_CTRL, len,
+ &temp3[0]);
+
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_SWITCH_REGULATOR_CTRL, len,
+ &temp3[0]);
+
+ temp3[0] = 0x3;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_SYSTEM_STATE_CFG, len,
+ &temp3[0]);
+
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_SYSTEM_STATE, len, &temp3[0]);
+
+ temp3[0] = 0x3;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL2, WRITE_SYSTEM_STATE_CFG, len,
+ &temp3[0]);
+
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL2, WRITE_SYSTEM_STATE, len, &temp3[0]);
+
+ temp3[0] = 0x2b;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_SYSTEM_STATE, len, &temp3[0]);
+
+ temp3[0] = 0x80;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_OPERATING_FUNCTION, len,
+ &temp3[0]);
+
+ temp3[0] = 0xe0;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_OPERATING_CONDITIONS, len,
+ &temp3[0]);
+
+ temp3[0] = 0x1;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ /* Set Switching Paramenters as for Le88266
+ * 1. BSI[1:0] = 00b (sense pin VBL is SWVSY, VBH is SWVSZ)
+ * 2. SWFS[1:0] = 00b (setting frequency as 384kHz in high power mode)
+ * 3. SWYV[4:0] = 00101b (setting to -25V)
+ * 4. SWZV[4:0] = 00000b (setting to 0V)
+ */
+ temp3[0] = 0x00;
+ temp3[1] = 0x05;
+ temp3[2] = 0x00;
+ len = 0x03;
+ slic_cmd(spi, CHANNEL1, WRITE_SWITCH_REGULATOR_PARAMS, len,
+ &temp3[0]);
+
+ /* Put the Switching regulators in
+ * 1. Regulator Y & Z in low power state
+ * 2. Over voltage protection enabled
+ */
+ temp3[0] = 0x15;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_SWITCH_REGULATOR_CTRL, len,
+ &temp3[0]);
+
+ /* Wait 20ms before switching from low power to high power */
+ mdelay(20);
+
+ temp3[0] = 0x9;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ temp3[0] = 0xb;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ temp3[0] = 0xb;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ temp3[0] = 0x1;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ temp3[0] = 0x1;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL2, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ temp3[0] = 0x2;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ temp3[0] = 0x2;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ temp3[0] = 0x3;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ temp3[0] = 0x3;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_CONVERTER_CFG, len, &temp3[0]);
+
+ /* Set Switching Paramenters as for Le88266
+ * 1. BSI[1:0] = 00b (sense pin VBL is SWVSY, VBH is SWVSZ)
+ * 2. SWFS[1:0] = 00b (setting frequency as 384kHz in high power mode)
+ * 3. SWYV[4:0] = 00101b (setting to -25V)
+ * 4. SWZV[4:0] = 00000b (setting to 0V)
+ */
+ temp3[0] = 0x00;
+ temp3[1] = 0x05;
+ temp3[2] = 0x00;
+ len = 0x03;
+ slic_cmd(spi, CHANNEL1, WRITE_SWITCH_REGULATOR_PARAMS, len,
+ &temp3[0]);
+
+ /* Put the Switching regulators in
+ * 1. Regulator Y & Z in high power state
+ * 2. Over voltage protection enabled
+ */
+ temp3[0] = 0x1f;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_SWITCH_REGULATOR_CTRL, len,
+ &temp3[0]);
+
+ /* Setting the channel specific parameters */
+ for (channel_id = CHANNEL1; channel_id <= CHANNEL2; channel_id++) {
+
+ /* Set the IO direction to Output - to energise the fxo
+ * relay */
+ temp3[0] = 0x1;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_IO_DIRECTION, len,
+ &temp3[0]);
+
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_IO_DATA, len,
+ &temp3[0]);
+
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_CONDITIONS,
+ len, &temp3[0]);
+
+ len = sizeof(dataset_cadenceTimer) / sizeof(unsigned char);
+ slic_cmd(spi, channel_id, WRITE_CADENCE_TIMER, len,
+ &dataset_cadenceTimer[0]);
+
+ temp3[0] = 0x2;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE_CFG,
+ len, &temp3[0]);
+
+ temp3[0] = 0xc0;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_CONDITIONS,
+ len, &temp3[0]);
+
+ temp3[0] = 0x0;
+ temp3[1] = 0x2;
+ len = 0x02;
+ slic_cmd(spi, channel_id, WRITE_DC_CALIBRATION, len,
+ &temp3[0]);
+
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE, len,
+ &temp3[0]);
+
+ len = sizeof(dataset_writeLoopParams) / sizeof(unsigned char);
+ slic_cmd(spi, channel_id,
+ WRITE_LOOP_SUPERVISION_PARAMS, len,
+ &dataset_writeLoopParams[0]);
+
+ temp3[0] = 0x13;
+ temp3[1] = 0x8;
+ len = 0x02;
+ slic_cmd(spi, channel_id, WRITE_DC_FEED_PARAMS, len,
+ &temp3[0]);
+
+ len = sizeof(dataset1_for_nooperation) / sizeof(unsigned char);
+ slic_cmd(spi, channel_id, WRITE_NO_OPERATION, len,
+ &dataset1_for_nooperation[0]);
+
+ temp3[0] = 0x3f;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_FUNCTION,
+ len, &temp3[0]);
+
+ len = sizeof(dataset2_for_nooperation) / sizeof(unsigned char);
+ slic_cmd(spi, channel_id, WRITE_NO_OPERATION, len,
+ &dataset2_for_nooperation[0]);
+
+ temp3[0] = 0x2;
+ len = 0x1;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE_CFG,
+ len, &temp3[0]);
+
+ len = sizeof(dataset_internalCfgReg3) / sizeof(unsigned char);
+ slic_cmd(spi, channel_id, WRITE_INTERNAL_CFG_REG3 ,
+ len, &dataset_internalCfgReg3[0]);
+
+ len = sizeof(dataset3_for_nooperation) / sizeof(unsigned char);
+ slic_cmd(spi, channel_id, WRITE_NO_OPERATION, len,
+ &dataset3_for_nooperation[0]);
+
+ temp3[0] = 0xbf;
+ len = 0x1;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_FUNCTION,
+ len, &temp3[0]);
+
+ temp3[0] = 0xc0;
+ len = 0x1;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_CONDITIONS,
+ len, &temp3[0]);
+
+ temp3[0] = 0x6;
+ len = 0x1;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE_CFG,
+ len, &temp3[0]);
+
+ temp3[0] = 0x6;
+ len = 0x1;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE_CFG,
+ len, &temp3[0]);
+
+ temp3[0] = 0xc0;
+ len = 0x1;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_CONDITIONS,
+ len, &temp3[0]);
+
+ temp3[0] = 0x16;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE_CFG,
+ len, &temp3[0]);
+
+ temp3[0] = 0xc0;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_CONDITIONS,
+ len, &temp3[0]);
+
+ temp3[0] = 0x16;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE_CFG,
+ len, &temp3[0]);
+
+ temp3[0] = 0x3f;
+ if (channel_id == CHANNEL1)
+ temp3[1] = 0xff;
+ else
+ temp3[1] = 0xbf;
+ len = 0x02;
+ slic_cmd(spi, channel_id, WRITE_INT_MASK, len,
+ &temp3[0]);
+
+ temp3[0] = 0x16;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE_CFG,
+ len, &temp3[0]);
+
+ temp3[0] = 0xc0;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_CONDITIONS,
+ len, &temp3[0]);
+
+ temp3[0] = 0x0;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_CONDITIONS,
+ len, &temp3[0]);
+
+ temp3[0] = 0x0;
+ temp3[1] = 0x2;
+ len = 0x02;
+ slic_cmd(spi, channel_id, WRITE_DC_CALIBRATION, len,
+ &temp3[0]);
+
+ temp3[0] = 0x2b;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE, len,
+ &temp3[0]);
+
+ }
+ /* Reading the Device Configuration register */
+ len = 0x1;
+ slic_cmd(spi, CHANNEL1, READ_DEVICE_CONFIGURATION, len,
+ &temp1);
+ printk(KERN_INFO "DEV reg is %x\n", temp1);
+
+ /* Enabling interrupt by writing into Device Configuration Register */
+ temp1 &= 0x7F;
+ len = 0x01;
+ slic_cmd(spi, CHANNEL1, WRITE_DEVICE_CFG, len, &temp1);
+
+ /* Reading the Device Configuration register */
+ len = 0x1;
+ slic_cmd(spi, CHANNEL1, READ_DEVICE_CONFIGURATION, len,
+ &temp1);
+ printk(KERN_INFO "DEV reg after is %x\n", temp1);
+
+ /* Reading the Mask register */
+ len = 0x2;
+ slic_cmd(spi, CHANNEL1, READ_INT_MASK, len, &temp2[0]);
+ printk(KERN_INFO "Mask reg before setting is %x %x\n",
+ temp2[0], temp2[1]);
+
+ /* Writing into the mask register */
+ temp2[0] = 0xF6;
+ temp2[1] = 0xF6;
+ len = 0x2;
+ slic_cmd(spi, CHANNEL1, WRITE_INT_MASK, len, &temp2[0]);
+
+ /* Reading the Mask register */
+ len = 0x2;
+ slic_cmd(spi, CHANNEL1, READ_INT_MASK, len, &temp2[0]);
+ printk(KERN_INFO "Mask reg after setting is %x %x\n",
+ temp2[0], temp2[1]);
+
+ temp1 = slic_id*4;
+ len = 0x1;
+ slic_cmd(spi, CHANNEL1, WRITE_TX_TIME_SLOT, len, &temp1);
+
+ len = 0x1;
+ slic_cmd(spi, CHANNEL1, READ_TX_TIME_SLOT, len, &temp1);
+ printk(KERN_INFO "Read Tx Timeslot for CH1 is %x\n", temp1);
+
+ temp1 = slic_id*4 + 2;
+ len = 0x1;
+ slic_cmd(spi, CHANNEL2, WRITE_TX_TIME_SLOT, len, &temp1);
+
+ len = 0x1;
+ slic_cmd(spi, CHANNEL2, READ_TX_TIME_SLOT, len, &temp1);
+ printk(KERN_INFO "Read Tx Timeslot for CH2 is %x\n", temp1);
+
+ temp1 = slic_id*4;
+ len = 0x1;
+ slic_cmd(spi, CHANNEL1, WRITE_RX_TIME_SLOT, len, &temp1);
+
+ len = 0x1;
+ slic_cmd(spi, CHANNEL1, READ_RX_TIME_SLOT, len, &temp1);
+ printk(KERN_INFO "Read Rx Timeslot for CH1 is %x\n", temp1);
+
+ temp1 = slic_id*4 + 2;
+ len = 0x1;
+ slic_cmd(spi, CHANNEL2, WRITE_RX_TIME_SLOT, len, &temp1);
+
+ len = 0x1;
+ slic_cmd(spi, CHANNEL2, READ_RX_TIME_SLOT, len, &temp1);
+ printk(KERN_INFO "Read Rx Timeslot for CH2 is %x\n", temp1);
+
+ for (channel_id = CHANNEL1; channel_id <= CHANNEL2; channel_id++) {
+
+ temp1 &= 0xBF;
+ temp1 |= 0x80;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_OPERATING_FUNCTION,
+ len, &temp1);
+
+ len = 0x01;
+ slic_cmd(spi, channel_id, READ_OPERATING_FUNCTION,
+ len, &temp1);
+ printk(KERN_INFO "Operating Fun for channel %d is %x\n",
+ channel_id, temp1);
+
+ /* Install Timers */
+ len = 0x04;
+ slic_cmd(spi, channel_id, READ_CADENCE_TIMER, len,
+ &cad[0]);
+ printk(KERN_INFO "Cadence Timer Reg for CH%d before is %x %x"
+ "%x %x\n", channel_id, cad[0], cad[1], cad[2],
+ cad[3]);
+
+ len = sizeof(set_cadenceTimer) / sizeof(unsigned char);
+ slic_cmd(spi, channel_id, WRITE_CADENCE_TIMER, len,
+ &set_cadenceTimer[0]);
+
+ len = 0x04;
+ slic_cmd(spi, channel_id, READ_CADENCE_TIMER , len,
+ &cad[0]);
+ printk(KERN_INFO "Cadence Timer Reg for CH%d after is %x %x"
+ "%x %x\n", channel_id, cad[0], cad[1], cad[2],
+ cad[3]);
+ temp1 = 0x20;
+ len = 0x01;
+ slic_cmd(spi, channel_id, WRITE_SYSTEM_STATE_CFG,
+ len, &temp1);
+
+ slic_cmd(spi, channel_id, READ_SYSTEM_STATE_CFG,
+ len, &temp1);
+ printk(KERN_INFO "Switching control for channel %d is %x\n",
+ channel_id, temp1);
+ }
+ num_slics++;
+ return 0;
+}
+
+void configure_spi_pdata(struct spi_device *spi)
+{
+ struct slic_platform_data *spi_slic_pdata;
+ static int num_slic;
+
+ spi_slic_pdata = kzalloc(sizeof(*spi_slic_pdata), GFP_KERNEL);
+ if (spi_slic_pdata == NULL)
+ return;
+
+ spi->dev.platform_data = spi_slic_pdata;
+
+ spi_slic_pdata->ch1_rx_slot = CH1_RX_SLOT_NUM + num_slic;
+ spi_slic_pdata->ch1_tx_slot = CH1_TX_SLOT_NUM + num_slic;
+ spi_slic_pdata->ch2_rx_slot = CH2_RX_SLOT_NUM + num_slic;
+ spi_slic_pdata->ch2_tx_slot = CH2_TX_SLOT_NUM + num_slic;
+ pr_info("SLIC config success\n");
+ num_slic = num_slic + SLIC_SLOT_OFFSET;
+
+}
+static int slic_remove(struct spi_device *spi)
+{
+
+ printk(KERN_INFO "SLIC module uninstalled\n");
+ return 0;
+}
+
+static int slic_probe(struct spi_device *spi)
+{
+ int ret = 0;
+ struct slic_platform_data *data;
+ struct tdm_phy_priv *p_tdm_phy_priv;
+ struct device_node *np = spi->dev.of_node;
+ const phandle *phandle_prop;
+ g_spi = spi;
+
+ printk(KERN_INFO "SLIC probed!\n");
+
+ p_tdm_phy_priv = kzalloc(sizeof(struct tdm_phy_priv), GFP_KERNEL);
+ if (!p_tdm_phy_priv) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ if (of_device_is_compatible(np, "tdm-phy-slic")) {
+ p_tdm_phy_priv->type = TDM_PHY_SLIC;
+ printk(KERN_INFO "TDM Phy type: SLIC\n");
+ } else if (of_device_is_compatible(np, "tdm-phy-e1")) {
+ p_tdm_phy_priv->type = TDM_PHY_E1;
+ printk(KERN_INFO "TDM Phy type: E1\n");
+ } else if (of_device_is_compatible(np, "tdm-phy-t1")) {
+ p_tdm_phy_priv->type = TDM_PHY_T1;
+ printk(KERN_INFO "TDM Phy type: T1\n");
+ } else {
+ printk(KERN_ERR "TDM_PHY: Unknown device type\n");
+ goto err_device_type;
+ }
+
+ p_tdm_phy_priv->device = &spi->dev;
+ p_tdm_phy_priv->phy.np = np;
+ p_tdm_phy_priv->phy.configure_phy = slic_init_configure;
+ phandle_prop = of_get_property(np, "phandle", NULL);
+ if (!phandle_prop)
+ printk(KERN_ERR "Can't get phy handle\n");
+ else {
+ p_tdm_phy_priv->phy.phandle_prop = (void *)phandle_prop;
+ add_tdm_phy(&p_tdm_phy_priv->phy);
+ }
+ spi->bits_per_word = 8;
+
+ if (num_slics >= MAX_NUM_OF_SLICS) {
+ printk(KERN_ERR "Exceeded the max number of slics\n");
+ return ret;
+ }
+
+ /* Initialize the SLIC */
+ configure_spi_pdata(spi);
+ data = spi->dev.platform_data;
+ slic_ch[num_slics].ch1_tx_slot = data->ch1_tx_slot;
+ slic_ch[num_slics].ch1_rx_slot = data->ch1_rx_slot;
+ slic_ch[num_slics].ch2_tx_slot = data->ch2_tx_slot;
+ slic_ch[num_slics].ch2_rx_slot = data->ch2_rx_slot;
+
+err_device_type:
+ kfree(p_tdm_phy_priv);
+err_alloc:
+ return ret;
+}
+
+static const struct of_device_id slic_match[] = {
+ {
+ .compatible = "zarlink,le88266",
+ },
+ {},
+};
+
+static struct spi_driver slic_driver = {
+ .driver = {
+ .name = "legerity",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ .of_match_table = slic_match,
+ },
+ .probe = slic_probe,
+ .remove = slic_remove,
+
+};
+
+static int __init slic_init(void)
+{
+ int ret;
+ printk(KERN_INFO "SLIC: " DRV_DESC "\n");
+ printk(KERN_INFO "####################################################"
+ "\n# This driver was created solely by Freescale, #"
+ "\n# without the assistance, support or intellectual #"
+ "\n# property of Zarlink Semiconductor. No #"
+ "\n# maintenance or support will be provided by #"
+ "\n# Zarlink Semiconductor regarding this driver. #"
+ "\n####################################################"
+ "\n");
+
+ ret = spi_register_driver(&slic_driver);
+ if (ret != 0)
+ printk(KERN_ERR "%s spi_register_driver failed\n",
+ __func__);
+ return ret;
+}
+
+static void __exit slic_exit(void)
+{
+ spi_unregister_driver(&slic_driver);
+}
+
+module_init(slic_init);
+module_exit(slic_exit);
diff --git a/drivers/tdm/line_ctrl/slic_zarlink.h b/drivers/tdm/line_ctrl/slic_zarlink.h
new file mode 100644
index 0000000..1a44590
--- /dev/null
+++ b/drivers/tdm/line_ctrl/slic_zarlink.h
@@ -0,0 +1,131 @@
+/*
+ * drivers/tdm/line/slic_zarlink.h
+ *
+ * Copyright (C) 2009-2012 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * This is the header file for the SLIC Driver Module
+ * drivers/tdm/line/slic_zarlink.c.
+ *
+ * Author: Rajesh Gumasta<rajesh.gumasta@freescale.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+#ifndef SLIC_ZARLINK_H
+#define SLIC_ZARLINK_H
+
+struct slic_platform_data {
+ unsigned int ch1_rx_slot;
+ unsigned int ch1_tx_slot;
+ unsigned int ch2_rx_slot;
+ unsigned int ch2_tx_slot;
+};
+
+/* SLIC channel configuration */
+#define CH1_RX_SLOT_NUM 0
+#define CH1_TX_SLOT_NUM 0
+#define CH2_RX_SLOT_NUM 2
+#define CH2_TX_SLOT_NUM 2
+
+#define SLIC_SLOT_OFFSET 2
+
+/* commands to the SLIC */
+#define CHANNEL1 0x01
+#define CHANNEL2 0x02
+#define HW_RESET 0x04
+#define WRITE_NO_OPERATION 0x06
+#define WRITE_TX_TIME_SLOT 0x40
+#define READ_TX_TIME_SLOT 0x41
+#define WRITE_RX_TIME_SLOT 0x42
+#define READ_RX_TIME_SLOT 0x43
+#define WRITE_TXRXCLKSLOT_TXCLKEDGE 0x44
+#define WRITE_DEVICE_CFG 0x46
+#define READ_DEVICE_CONFIGURATION 0x47
+#define WRITE_CHANNEL_ENABLE 0X4A
+#define WRITE_IO_DATA 0x52
+#define WRITE_IO_DIRECTION 0x54
+#define WRITE_SYSTEM_STATE 0x56
+#define WRITE_OPERATING_FUNCTION 0x60
+#define READ_OPERATING_FUNCTION 0x61
+#define WRITE_SYSTEM_STATE_CFG 0x68
+#define READ_SYSTEM_STATE_CFG 0x69
+#define WRITE_INT_MASK 0x6C
+#define READ_INT_MASK 0x6D
+#define WRITE_OPERATING_CONDITIONS 0x70
+#define READ_PRODUCT_CODE 0X73
+#define WRITE_CONVERTER_CFG 0xA6
+#define WRITE_LOOP_SUPERVISION_PARAMS 0xC2
+#define WRITE_DC_FEED_PARAMS 0xC6
+#define WRITE_CADENCE_TIMER 0xE0
+#define READ_CADENCE_TIMER 0xE1
+#define WRITE_SWITCH_REGULATOR_PARAMS 0xE4
+#define WRITE_SWITCH_REGULATOR_CTRL 0xE6
+#define WRITE_INTERNAL_CFG_REG3 0xF2
+#define WRITE_DC_CALIBRATION 0xFC
+
+/* Dataset1 for no operation command */
+static unsigned char dataset1_for_nooperation[] = {
+ 0xca, 0xfa, 0x98, 0xca, 0xb9,
+ 0xa2, 0x4c, 0x2b, 0xa2, 0xa3,
+ 0xa2, 0xae, 0x2b, 0x9a, 0x23,
+ 0xca, 0x26, 0x9f, 0x1, 0x8a,
+ 0x1d, 0x1, 0x1, 0x11, 0x1,
+ 0x90, 0x1, 0x90, 0x1, 0x90,
+ 0x1, 0x90, 0x1, 0x90, 0x88,
+ 0xd8, 0x70, 0x7a, 0x87, 0x23,
+ 0x3f, 0x4a, 0x97, 0x5a, 0xa7,
+ 0x5a, 0xaf, 0x82, 0x22, 0xe0,
+ 0x80, 0x32, 0x10, 0x50, 0x10,
+ 0x86, 0xa2, 0x63, 0x23, 0xbb,
+ 0x2a, 0xa4, 0x29, 0x7d, 0x87,
+ 0x2a, 0xfa, 0x8f, 0x29, 0xf0,
+ 0x96, 0x2e, 0x1
+};
+
+/* Dataset2 for no operation command */
+static unsigned char dataset2_for_nooperation[] = {
+ 0xd2, 0x0, 0x0, 0x0, 0x0,
+ 0x36, 0x36, 0xb9, 0x0, 0x0,
+ 0x0, 0x0, 0x68, 0x0
+};
+
+/* Dataset3 for no operation command */
+static unsigned char dataset3_for_nooperation[] = {
+ 0xc2, 0x1b, 0x84, 0xb4, 0x5,
+ 0xc6, 0x8, 0x8
+};
+
+/* Dataset for internal configuration register 3 command */
+static unsigned char dataset_internalCfgReg3[] = {
+ 0x10, 0x1, 0x0, 0x0
+};
+
+/* Dataset for cadence timer command */
+static unsigned char dataset_cadenceTimer[] = {
+ 0x3f, 0xff, 0x0, 0x0
+};
+
+/* Dataset for Loop parameters command */
+static unsigned char dataset_writeLoopParams[] = {
+ 0x1b, 0x84, 0xb3, 0x5
+};
+
+/* Dataset1 for cadence timer command */
+static unsigned char set_cadenceTimer[] = {
+ 0x01, 0x90, 0x03, 0x20
+};
+
+#endif
--
1.7.6.GIT
^ permalink raw reply related
* [PATCH 3/4][v2] Added TDM device support and Freescale Starlite driver
From: Sandeep Singh @ 2013-03-05 12:41 UTC (permalink / raw)
To: linuxppc-dev; +Cc: Sandeep Singh, Poonam Aggrwal, linux-arm-kernel
In-Reply-To: <1362487297-19702-1-git-send-email-Sandeep@freescale.com>
Freescale TDM controller consists of a TDM module supporting 128 channels
running at up to 50 Mbps with 8-bit and 16-bit word size. The TDM bus connects
gluelessly to most T1/E1 frames as well as to common buses such as the H.110,
SCAS, and MVIP. TDM also supports an I2S mode. The TDM module operates in
independent or shared mode when receiving or transmitting data.
This controller is available on MPC8315, P1010, P1020, P1022 and P1024 Freescale SOCs.
The driver registers itself with the TDM Framework & provides TDM functionality to the client modules.
In its present form this driver supports only channelised mode.
Signed-off-by: Sandeep Singh <Sandeep@freescale.com>
Signed-off-by: Poonam Aggrwal <poonam.aggrwal@freescale.com>
---
drivers/tdm/Kconfig | 1 +
drivers/tdm/Makefile | 2 +-
drivers/tdm/device/Kconfig | 15 +
drivers/tdm/device/Makefile | 9 +
drivers/tdm/device/tdm_fsl.c | 916 ++++++++++++++++++++++++++++++++++++++++++
drivers/tdm/device/tdm_fsl.h | 444 ++++++++++++++++++++
6 files changed, 1386 insertions(+), 1 deletions(-)
create mode 100644 drivers/tdm/device/Kconfig
create mode 100644 drivers/tdm/device/Makefile
create mode 100644 drivers/tdm/device/tdm_fsl.c
create mode 100644 drivers/tdm/device/tdm_fsl.h
diff --git a/drivers/tdm/Kconfig b/drivers/tdm/Kconfig
index 8db2b05..87d6929 100644
--- a/drivers/tdm/Kconfig
+++ b/drivers/tdm/Kconfig
@@ -22,4 +22,5 @@ config TDM_DEBUG_CORE
messages to the system log. Select this if you are having a
problem with TDM support and want to see more of what is going on.
+source drivers/tdm/device/Kconfig
endif # TDM
diff --git a/drivers/tdm/Makefile b/drivers/tdm/Makefile
index 5569616..347d3f7 100644
--- a/drivers/tdm/Makefile
+++ b/drivers/tdm/Makefile
@@ -2,7 +2,7 @@
# Makefile for the TDM core.
#
-obj-$(CONFIG_TDM) += tdm-core.o
+obj-$(CONFIG_TDM) += tdm-core.o device/
ifeq ($(CONFIG_TDM_DEBUG_CORE),y)
EXTRA_CFLAGS += -DDEBUG
endif
diff --git a/drivers/tdm/device/Kconfig b/drivers/tdm/device/Kconfig
new file mode 100644
index 0000000..9fd1b06
--- /dev/null
+++ b/drivers/tdm/device/Kconfig
@@ -0,0 +1,15 @@
+#
+# TDM device configuration
+#
+
+menu "TDM Device support"
+
+config TDM_FSL
+ tristate "Driver for Freescale TDM controller"
+ depends on FSL_SOC
+ ---help---
+ This is a driver for Freescale TDM controller. The controller
+ is found in various Freescale SOCs viz MPC8315, P1020. The TDM driver
+ basically multiplexes and demultiplexes data from different channels.
+ The TDM can interface SLIC kind of devices.
+endmenu
diff --git a/drivers/tdm/device/Makefile b/drivers/tdm/device/Makefile
new file mode 100644
index 0000000..4156d7f
--- /dev/null
+++ b/drivers/tdm/device/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for the TDM device drivers.
+#
+
+obj-y += tdm_fsl.o
+
+#ifeq ($(CONFIG_TDM_DEBUG_BUS),y)
+#EXTRA_CFLAGS += -DDEBUG
+#endif
diff --git a/drivers/tdm/device/tdm_fsl.c b/drivers/tdm/device/tdm_fsl.c
new file mode 100644
index 0000000..9c3f483
--- /dev/null
+++ b/drivers/tdm/device/tdm_fsl.c
@@ -0,0 +1,916 @@
+/*
+ * drivers/tdm/tdm_fsl.c
+ *
+ * Copyright (C) 2007-2012 Freescale Semiconductor, Inc, All rights reserved.
+ *
+ * TDM driver for Freescale TDM controller.
+ * This driver can interface with SLIC device to run VOIP kind of
+ * applications.
+ *
+ * Author: P. V. Suresh <pala@freescale.com>
+ * Hemant Agrawal <hemant@freescale.com>
+ * Rajesh Gumasta <rajesh.gumasta@freescale.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+ /* Note that this is a complete rewrite of P.V. Suresh's driver code.
+ But we have used so much of his original code and ideas that it seems
+ only fair to recognize him as co-author -- Rajesh & Hemant */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/tdm.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/dma-mapping.h>
+#include <linux/spinlock.h>
+#include <sysdev/fsl_soc.h>
+
+#include "tdm_fsl.h"
+
+#define DRV_DESC "Freescale TDM Driver Adapter"
+#define DRV_NAME "fsl_tdm"
+
+static int tdmen = 1;
+
+module_param(tdmen, int, S_IRUSR);
+MODULE_PARM_DESC(tdmen, "Enable TDM: Enable=1, Disable=0(default)");
+
+/* Initialize the Tx Transfer Control Discriptor parameters*/
+static void tx_tcd_init(struct tdm_priv *priv)
+{
+ int i;
+ u32 iter;
+ u32 offset;
+ dma_addr_t physaddr;
+ struct tdm_adapter *adap;
+ int bytes_in_fifo_per_frame;
+ adap = priv->adap;
+ if (!adap) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return;
+ }
+ bytes_in_fifo_per_frame =
+ ALIGN_SIZE(adap->adapt_cfg.num_ch * adap->adapt_cfg.slot_width, 8);
+
+ iter = (bytes_in_fifo_per_frame / NBYTES) * adap->adapt_cfg.num_frames;
+
+ for (i = 0; i < NUM_OF_TDM_BUF; i++) {
+ offset = i * adap->adapt_cfg.num_frames *
+ bytes_in_fifo_per_frame;
+ /* saddr */
+ priv->dma_tx_tcd[i]->tcd[0] = (u32)priv->dma_output_paddr
+ + offset;
+
+ /* ssize=dsize=64bit, soff=8, smod=dmod=0 */
+ priv->dma_tx_tcd[i]->tcd[1] =
+ DMA_TCD1_SOFF(SOFF_VAL) | DMA_TCD1_SSIZE(SSIZE_64BITS) |
+ DMA_TCD1_DSIZE(SSIZE_64BITS);
+
+ /* number of bytes for minor loop, wide fifo 8bytes for dma */
+ priv->dma_tx_tcd[i]->tcd[2] = NBYTES;
+
+ /* slast = 0 */
+ priv->dma_tx_tcd[i]->tcd[3] = SLAST;
+
+ /* dadr = TX FIFO */
+ priv->dma_tx_tcd[i]->tcd[4] = TDM_TDR_OFFSET + priv->ptdm_base;
+
+ /* channel to channel linking is disabled ,
+ * destination offset is inc destination adr by 8,
+ * current iteration(citer) = number of transfers for frame
+ */
+ priv->dma_tx_tcd[i]->tcd[5] = DMA_TCD5_CITER_DISABLE_LINK(iter);
+
+ /* enable scater gather, interrupt on 1 Frame, */
+ priv->dma_tx_tcd[i]->tcd[7] =
+ DMA_TCD7_BITER_DISABLE_LINK(iter) | DMA_TCD7_E_SG;
+ priv->dma_tx_tcd[i]->tcd[6] = SLAST_SGA;
+ }
+
+ /* Next TCD for SG operation */
+ physaddr = priv->dma_tx_tcd_paddr;
+ priv->dma_tx_tcd[2]->tcd[6] =
+ ALIGN_SIZE(physaddr, ALIGNED_32_BYTES);
+ physaddr += TCD_BUFFER_SIZE;
+ priv->dma_tx_tcd[0]->tcd[6] =
+ ALIGN_SIZE(physaddr, ALIGNED_32_BYTES);
+ physaddr += TCD_BUFFER_SIZE;
+ priv->dma_tx_tcd[1]->tcd[6] =
+ ALIGN_SIZE(physaddr, ALIGNED_32_BYTES);
+}
+
+/* Initialize the Rx Transfer Control Discriptor parameters*/
+static void rx_tcd_init(struct tdm_priv *priv)
+{
+ int i;
+ u32 iter;
+ u32 offset;
+ dma_addr_t physaddr;
+ struct tdm_adapter *adap;
+ int bytes_in_fifo_per_frame;
+ adap = priv->adap;
+ if (!adap) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return;
+ }
+ bytes_in_fifo_per_frame =
+ ALIGN_SIZE(adap->adapt_cfg.num_ch * adap->adapt_cfg.slot_width, 8);
+
+ iter = (bytes_in_fifo_per_frame / NBYTES) * adap->adapt_cfg.num_frames;
+
+ for (i = 0; i < NUM_OF_TDM_BUF; i++) {
+ /* TDM RX fifo address */
+ priv->dma_rx_tcd[i]->tcd[0] = TDM_RDR_OFFSET + priv->ptdm_base;
+
+ /* ssize=dsize=64bit, soff=smod=dmod=0 */
+ priv->dma_rx_tcd[i]->tcd[1] =
+ DMA_TCD1_SSIZE(SSIZE_64BITS) | DMA_TCD1_DSIZE(SSIZE_64BITS);
+
+ /* number of bytes for minor loop, wide fifo 8bytes for dma */
+ priv->dma_rx_tcd[i]->tcd[2] = NBYTES;
+
+ /* slast = 0 */
+ priv->dma_rx_tcd[i]->tcd[3] = SLAST;
+
+ offset = i * adap->adapt_cfg.num_frames *
+ bytes_in_fifo_per_frame;
+
+ /* dadr = rx buffer address */
+ priv->dma_rx_tcd[i]->tcd[4] = (u32)priv->dma_input_paddr
+ + offset;
+
+ /* channel to channel linking is disabled ,
+ * destination offset is inc destination adr by 8,
+ * current iteration(citer) = number of transfers for frame
+ */
+ priv->dma_rx_tcd[i]->tcd[5] =
+ DMA_TCD5_DOFF(DOFF_VAL) | DMA_TCD5_CITER_DISABLE_LINK(iter);
+
+ /* enable scater gather, interrupt on 1 Frame, */
+ priv->dma_rx_tcd[i]->tcd[7] =
+ DMA_TCD7_BITER_DISABLE_LINK(iter) | DMA_TCD7_E_SG |
+ DMA_TCD7_INT_MAJ;
+ priv->dma_rx_tcd[i]->tcd[6] = DLAST_SGA;
+ }
+
+ /* Next TCD for SG operation */
+ physaddr = priv->dma_rx_tcd_paddr;
+ priv->dma_rx_tcd[2]->tcd[6] =
+ ALIGN_SIZE(physaddr, ALIGNED_32_BYTES);
+ physaddr += TCD_BUFFER_SIZE;
+ priv->dma_rx_tcd[0]->tcd[6] =
+ ALIGN_SIZE(physaddr, ALIGNED_32_BYTES);
+ physaddr += TCD_BUFFER_SIZE;
+ priv->dma_rx_tcd[1]->tcd[6] =
+ ALIGN_SIZE(physaddr, ALIGNED_32_BYTES);
+}
+
+static irqreturn_t tdm_err_isr(int irq, void *p)
+{
+ int ret = IRQ_NONE;
+ u32 status, mask, val;
+ u32 dmac_err;
+ struct tdm_priv *priv;
+ u32 ch;
+ priv = p;
+
+ if (!priv) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ /* transmit errors */
+ status = in_be32(&priv->tdm_regs->ter);
+ mask = in_be32(&priv->tdm_regs->tier);
+ val = status & mask;
+ out_be32(&priv->tdm_regs->ter, val);
+
+ /* Transmit under Run error */
+ if (val & TIER_TUEE)
+ dev_err(priv->device, "TDM::Transmit Under Run error\n");
+
+ /* Transmit Sync Error */
+ if (val & TIER_TSEEE)
+ dev_err(priv->device, "TDM::Transmit Sync error\n");
+
+ if (val)
+ ret = IRQ_HANDLED;
+
+ /* receive errors */
+ status = in_be32(&priv->tdm_regs->rer);
+ mask = in_be32(&priv->tdm_regs->rier);
+ val = status & mask;
+ out_be32(&priv->tdm_regs->rer, val);
+
+ /* Receiver Over run error */
+ if (val & RIER_ROEE)
+ dev_err(priv->device, "TDM::Receive Over Run error\n");
+
+ /* Receive Sync Error */
+ if (val & RIER_RSEEE)
+ dev_err(priv->device, "TDM::Receive Sync error\n");
+
+ if (val)
+ ret = IRQ_HANDLED;
+
+ /* Handling of DMA Errors */
+ dmac_err = in_be32(&priv->dmac_regs->dmaes);
+ if (!(dmac_err & DMAES_VLD))
+ return ret;
+
+ ch = DMAES_ERRCHN(dmac_err);
+
+ if (dmac_err & DMAES_CPE)
+ dev_err(priv->device, "TDM::Channel priority error\n");
+ if (dmac_err & DMAES_GPE)
+ dev_err(priv->device, "TDM::Group priority error\n");
+ if (dmac_err & DMAES_SAE)
+ dev_err(priv->device, "TDM::Source address error\n");
+ if (dmac_err & DMAES_SOE)
+ dev_err(priv->device, "TDM::Source offset error\n");
+ if (dmac_err & DMAES_DAE)
+ dev_err(priv->device, "TDM::Destination address error\n");
+ if (dmac_err & DMAES_DOE)
+ dev_err(priv->device, "TDM::Destination offset error\n");
+ if (dmac_err & DMAES_NCE)
+ dev_err(priv->device, "TDM::Nbytes citer error\n");
+ if (dmac_err & DMAES_SGE)
+ dev_err(priv->device, "TDM::Scatter gather error\n");
+ if (dmac_err & DMAES_DBE)
+ dev_err(priv->device, "TDM::Destination bus error\n");
+ if (dmac_err & DMAES_SBE)
+ dev_err(priv->device, "TDM::Source bus error\n");
+
+ /* Clear the error */
+ out_8(&priv->dmac_regs->dmacerr, (u8)ch);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dmac_done_isr(int irq, void *p)
+{
+ u32 ch;
+ int ret = IRQ_NONE;
+ struct tdm_priv *priv;
+
+ priv = p;
+ if (!priv) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ ch = in_be32(&priv->dmac_regs->dmaintl);
+
+ /* clear interrupt */
+ if (ch & DMAC_RX_INT) {
+ out_8(&priv->dmac_regs->dmacint, TDMRX_DMA_CH);
+ ret = IRQ_HANDLED;
+ /* track phases for Rx/Tx */
+ priv->phase_rx += 1;
+ if (priv->phase_rx == NUM_OF_TDM_BUF)
+ priv->phase_rx = 0;
+ }
+ if (ch & DMAC_TX_INT) {
+ out_8(&priv->dmac_regs->dmacint, TDMTX_DMA_CH);
+ ret = IRQ_HANDLED;
+ }
+
+ if (ret == IRQ_HANDLED) {
+ /* set the flag and wake up the thread */
+ priv->adap->tdm_rx_flag = 1;
+
+ /* schedule the tasklet */
+ if (priv->adap->tasklet_conf)
+ tasklet_schedule(&priv->adap->tdm_data_tasklet);
+ }
+ return ret;
+}
+
+static int init_tdm(struct tdm_priv *priv)
+{
+ u8 *buf;
+ int i;
+ int buf_size;
+ dma_addr_t physaddr = 0;
+ int ret = 0;
+ struct tdm_adapter *adap;
+
+ if (!priv) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ adap = priv->adap;
+
+ /*
+ Allocate memory for Rx/Tx buffer according to active time slots
+ BufferSize = NUM_OF_TDM_BUF * NUM_SAMPLES_PER_FRAME * slot_width *
+ num_ch
+ */
+ /*Allocating Rx Buffer*/
+ buf_size = TDM_BUF_SIZE(adap->adapt_cfg.num_ch,
+ adap->adapt_cfg.slot_width,
+ adap->adapt_cfg.num_frames);
+ buf = dma_alloc_coherent(priv->device, buf_size, &physaddr, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_alloc_ip;
+ }
+ priv->dma_input_paddr = physaddr;
+ priv->dma_input_vaddr = buf;
+ priv->tdm_input_data = ALIGN_ADDRESS(buf, ALIGNED_8_BYTES);
+
+ /*Allocating Tx Buffer*/
+ buf = dma_alloc_coherent(priv->device, buf_size, &physaddr, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_alloc_op;
+ }
+ priv->dma_output_paddr = physaddr;
+ priv->dma_output_vaddr = buf;
+ priv->tdm_output_data = ALIGN_ADDRESS(buf, ALIGNED_8_BYTES);
+
+ /* allocate memory for TCD buffer discriptors */
+ buf = dma_alloc_coherent(priv->device, NUM_OF_TDM_BUF * TCD_BUFFER_SIZE,
+ &physaddr, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_alloc_rx;
+ }
+
+ memset(buf, 0, NUM_OF_TDM_BUF * TCD_BUFFER_SIZE);
+ priv->dma_rx_tcd_paddr = physaddr;
+ priv->dma_rx_tcd_vaddr = buf;
+ for (i = 0; i < NUM_OF_TDM_BUF; i++) {
+ priv->dma_rx_tcd[i] = ALIGN_ADDRESS(buf, ALIGNED_32_BYTES);
+ buf += TCD_BUFFER_SIZE;
+ }
+
+ buf = dma_alloc_coherent(priv->device, 3 * TCD_BUFFER_SIZE, &physaddr,
+ GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_alloc_tx;
+ }
+ memset(buf, 0, NUM_OF_TDM_BUF * TCD_BUFFER_SIZE);
+ priv->dma_tx_tcd_paddr = physaddr;
+ priv->dma_tx_tcd_vaddr = buf;
+ for (i = 0; i < NUM_OF_TDM_BUF; i++) {
+ priv->dma_tx_tcd[i] = ALIGN_ADDRESS(buf, ALIGNED_32_BYTES);
+ buf += TCD_BUFFER_SIZE;
+ }
+
+ priv->phase_rx = 0;
+ priv->phase_tx = 0;
+ return 0;
+
+err_alloc_tx:
+ dma_free_coherent(priv->device, NUM_OF_TDM_BUF * TCD_BUFFER_SIZE,
+ priv->dma_rx_tcd_vaddr, priv->dma_rx_tcd_paddr);
+err_alloc_rx:
+ dma_free_coherent(priv->device, buf_size, priv->dma_output_vaddr,
+ priv->dma_output_paddr);
+err_alloc_op:
+ dma_free_coherent(priv->device, buf_size, priv->dma_input_vaddr,
+ priv->dma_input_paddr);
+err_alloc_ip:
+ return ret;
+}
+
+/* TDM register programming */
+static int tdm_fsl_reg_init(struct tdm_priv *priv)
+{
+ int i;
+ int ch_size_type;
+ struct tdm_adapter *adap;
+ if (!priv) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+ adap = priv->adap;
+
+ /* channel/group round robin */
+ out_be32(&priv->dmac_regs->dmacr, DMACR_ERGA | DMACR_ERCA);
+ /* Enable error Interrupts for TDM Rx &Tx */
+ out_8(&priv->dmac_regs->dmaseei, TDMTX_DMA_CH);
+ out_8(&priv->dmac_regs->dmaseei, TDMRX_DMA_CH);
+ out_be32(&priv->dmac_regs->dmagpor, DMAGPOR_SNOOP);
+
+ tx_tcd_init(priv);
+ rx_tcd_init(priv);
+
+ /* TDM RD->TD loopback, Share T/R Fsync,Clock */
+ if (adap->adapt_cfg.loopback)
+ out_be32(&priv->tdm_regs->gir, GIR_LPBK | GIR_RTS);
+ else
+ out_be32(&priv->tdm_regs->gir, GIR_RTS);
+
+ /*
+ Rx Water mark 0, FIFO enable, Wide fifo, DMA enable for RX,
+ Receive Sync out, syncwidth = ch width, Rx clk out,zero sync,
+ falling edge , data order
+ */
+
+ out_be32(&priv->tdm_regs->rir,
+ RIR_RFWM(RIR_RFWM_VAL) | RIR_RFEN | RIR_RWEN | RIR_RDMA |
+ RIR_RSL | RIR_RSO | RIR_RCOE | RIR_RRDO |
+ RIR_RFSD(RIR_RFSD_VAL));
+ out_be32(&priv->tdm_regs->tir,
+ TIR_TFWM(TIR_RFWM_VAL) | TIR_TFEN | TIR_TWEN | TIR_TDMA |
+ TIR_TSL | TIR_TSO | TIR_TRDO | TIR_TFSD(TIR_RFSD_VAL));
+
+ /* no of channels ,Channel size-coading */
+ switch (adap->adapt_cfg.ch_size_type) {
+ case CHANNEL_8BIT_LIN:
+ ch_size_type = CHANNEL_8BIT_LIN;
+ break;
+ case CHANNEL_8BIT_ULAW:
+ ch_size_type = CHANNEL_8BIT_ULAW;
+ break;
+ case CHANNEL_8BIT_ALAW:
+ ch_size_type = CHANNEL_8BIT_ALAW;
+ break;
+ case CHANNEL_16BIT_LIN:
+ ch_size_type = CHANNEL_16BIT_LIN;
+ break;
+ default:
+ pr_err("%s:Invalid channel_size_type.\n"
+ "Setting channel to default size: 16 bits", __func__);
+ ch_size_type = CHANNEL_16BIT_LIN;
+
+ }
+ out_be32(&priv->tdm_regs->rfp,
+ RFP_RNCF(adap->adapt_cfg.num_ch) | RFP_RCS(ch_size_type));
+ out_be32(&priv->tdm_regs->tfp,
+ TFP_TNCF(adap->adapt_cfg.num_ch) | TFP_TCS(ch_size_type));
+
+ out_be32(&priv->tdm_regs->rier, 0);
+ out_be32(&priv->tdm_regs->tier, 0);
+
+ /* clear all receive and transmit chs */
+ for (i = 0; i < 4; i++) {
+ out_be32(&priv->tdm_regs->tcma[i], 0);
+ out_be32(&priv->tdm_regs->tcen[i], 0);
+ out_be32(&priv->tdm_regs->rcen[i], 0);
+ }
+
+ return 0;
+
+}
+
+static void tdm_fsl_stop(struct tdm_priv *priv)
+{
+ /* stop the Tx & Rx */
+ out_be32(&priv->tdm_regs->tcr, 0);
+ out_be32(&priv->tdm_regs->rcr, 0);
+
+ /* Clear DMA error Enable Request DMAEEIH/L */
+ out_8(&priv->dmac_regs->dmaceei, TDMTX_DMA_CH);
+ out_8(&priv->dmac_regs->dmaceei, TDMRX_DMA_CH);
+ out_8(&priv->dmac_regs->dmacint, TDMRX_DMA_CH);
+ out_8(&priv->dmac_regs->dmacint, TDMTX_DMA_CH);
+
+ /* disable the dma request */
+ out_8(&priv->dmac_regs->dmacerq, TDMRX_DMA_CH);
+ out_8(&priv->dmac_regs->dmacerq, TDMTX_DMA_CH);
+}
+
+static int tdm_fsl_disable(struct tdm_adapter *adap)
+{
+ struct tdm_priv *priv;
+ if (!adap) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+ priv = tdm_get_adapdata(adap);
+ if (priv->tdm_active == 0) {
+ dev_warn(priv->device, "already Disabled");
+ return 0;
+ }
+
+ priv->tdm_active = 0;
+
+ return 0;
+}
+
+static int tdm_fsl_enable(struct tdm_adapter *adap)
+{
+ int i;
+ u32 ch_enab[4];
+ unsigned long timeout;
+ struct tdm_priv *priv;
+ u32 ph;
+ if (!adap) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+ priv = tdm_get_adapdata(adap);
+ ph = priv->phase_tx;
+
+ if (priv->tdm_active == 1) {
+ dev_warn(priv->device, "already Enabled");
+ return 0;
+ }
+
+ /* enable the Channels required 0 to number of ch -1 */
+ for (i = 0; i < NUM_TDMTCEN_REG; i++)
+ ch_enab[i] = 0;
+
+ for (i = 0; i < adap->adapt_cfg.num_ch; i++)
+ ch_enab[i / TDMTCEN_REG_LEN] |= (1 << (i & 0x1F));
+
+ for (i = 0; i < NUM_TDMTCEN_REG; i++) {
+ out_be32(&priv->tdm_regs->rcen[i], ch_enab[i]);
+ out_be32(&priv->tdm_regs->tcen[i], ch_enab[i]);
+ }
+
+ /* Clear the DONE bit */
+ out_8(&priv->dmac_regs->dmacdne, TDMRX_DMA_CH);
+ out_8(&priv->dmac_regs->dmacdne, TDMTX_DMA_CH);
+
+ /* Load the Tx transfer control descriptors */
+ for (i = 0; i < DMA_MAX_TCD; i++)
+ out_be32(&priv->dmac_regs->tcd[TDMTX_DMA_CH].tcd[i],
+ priv->dma_tx_tcd[ph]->tcd[i]);
+
+ /* Load the Rx transfer control descriptors */
+ for (i = 0; i < DMA_MAX_TCD; i++)
+ out_be32(&priv->dmac_regs->tcd[TDMRX_DMA_CH].tcd[i],
+ priv->dma_rx_tcd[ph]->tcd[i]);
+
+ /* enable the dma request */
+ out_8(&priv->dmac_regs->dmaserq, TDMRX_DMA_CH);
+ out_8(&priv->dmac_regs->dmaserq, TDMTX_DMA_CH);
+
+ /* Enable Receiver, transmitter */
+ timeout = jiffies + TDM_ENABLE_TIMEOUT;
+ out_be32(&priv->tdm_regs->tcr, TCR_TEN);
+ while (!(in_be32(&priv->tdm_regs->tsr) & TSR_TENS)) {
+ if (time_after(jiffies, timeout)) {
+ dev_err(priv->device, "timeout to enable TDM Tx\n");
+ return -ETIMEDOUT;
+ }
+ cpu_relax();
+ }
+
+ timeout = jiffies + TDM_ENABLE_TIMEOUT;
+ out_be32(&priv->tdm_regs->rcr, RCR_REN);
+ while (!(in_be32(&priv->tdm_regs->rsr) & RSR_RENS)) {
+ if (time_after(jiffies, timeout)) {
+ dev_err(priv->device, "timeout to enable TDM Rx\n");
+ return -ETIMEDOUT;
+ }
+ cpu_relax();
+
+ }
+
+ priv->tdm_active = 1;
+ return 1;
+}
+static u32 tdm_fsl_read(struct tdm_adapter *adap,
+ u16 **input_tdm_buffer)
+{
+ struct tdm_priv *priv;
+ u32 phase_rx;
+ u32 buf_addr, buf_size;
+ int bytes_in_fifo_per_frame;
+ if (!adap) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+ /* point to where to start for the current phase data processing */
+ bytes_in_fifo_per_frame =
+ ALIGN_SIZE(adap->adapt_cfg.num_ch * adap->adapt_cfg.slot_width, 8);
+
+ priv = tdm_get_adapdata(adap);
+ if (!priv) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ if (priv->tdm_active == 0) {
+ dev_warn(priv->device, "TDM is not ready");
+ return 0;
+ }
+
+ if (priv->phase_rx == 0)
+ phase_rx = NUM_OF_TDM_BUF - 1;
+ else
+ phase_rx = priv->phase_rx - 1;
+
+ buf_size = bytes_in_fifo_per_frame * adap->adapt_cfg.num_frames;
+ buf_addr = buf_size * phase_rx;
+ *input_tdm_buffer = (u16 *)(priv->tdm_input_data + buf_addr);
+
+ return buf_size;
+}
+
+static u32 tdm_fsl_get_write_buf(struct tdm_adapter *adap,
+ u16 **output_tdm_buffer)
+{
+ struct tdm_priv *priv;
+ u32 tmp;
+ u32 phase_tx;
+ u32 buf_addr, buf_size;
+ int bytes_in_fifo_per_frame;
+
+ if (!adap) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+ /* point to where to start for the current phase data processing */
+ bytes_in_fifo_per_frame =
+ ALIGN_SIZE(adap->adapt_cfg.num_ch * adap->adapt_cfg.slot_width, 8);
+
+ priv = tdm_get_adapdata(adap);
+ if (!priv) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ if (priv->tdm_active == 0) {
+ dev_warn(priv->device, "TDM is not ready");
+ return 0;
+ }
+
+ tmp = in_be32(&priv->dmac_regs->tcd[TDMTX_DMA_CH].tcd[0]);
+
+ tmp -= priv->dma_tx_tcd[0]->tcd[0];
+
+ priv->phase_tx = tmp/(bytes_in_fifo_per_frame *
+ adap->adapt_cfg.num_frames);
+
+ if (priv->phase_tx == 0)
+ phase_tx = NUM_OF_TDM_BUF - 1;
+ else
+ phase_tx = priv->phase_tx - 1;
+
+ buf_size = bytes_in_fifo_per_frame * adap->adapt_cfg.num_frames;
+ buf_addr = buf_size * phase_tx;
+ *output_tdm_buffer = (u16 *)(priv->tdm_output_data + buf_addr);
+
+ return buf_size;
+}
+
+static const struct tdm_adapt_algorithm tdm_algo = {
+ .tdm_read = tdm_fsl_read,
+ .tdm_get_write_buf = tdm_fsl_get_write_buf,
+ .tdm_enable = tdm_fsl_enable,
+ .tdm_disable = tdm_fsl_disable,
+};
+
+static struct tdm_adapter tdm_fsl_ops = {
+ .owner = THIS_MODULE,
+ .name = "fsl_tdm",
+ .algo = &tdm_algo,
+};
+
+static int tdm_fsl_probe(struct platform_device *ofdev)
+{
+ int ret = 0;
+ int i;
+ int *tdm_tx_clk = NULL;
+ int *p_num_phy;
+ struct tdm_priv *priv;
+ struct resource res;
+ const phandle *phandle_prop;
+ struct device_node *np = ofdev->dev.of_node;
+
+ priv = kzalloc(sizeof(struct tdm_priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ dev_set_drvdata(&ofdev->dev, priv);
+ priv->device = &ofdev->dev;
+ ret = of_address_to_resource(ofdev->dev.of_node, 0, &res);
+ if (ret) {
+ ret = -EINVAL;
+ goto err_resource;
+ }
+
+ priv->ptdm_base = (u32)res.start;
+ priv->tdm_regs = of_iomap(ofdev->dev.of_node, 0);
+ if (!priv->tdm_regs) {
+ ret = -ENOMEM;
+ goto err_tdmregs;
+ }
+
+ priv->dmac_regs = of_iomap(ofdev->dev.of_node, 1);
+ if (!priv->dmac_regs) {
+ ret = -ENOMEM;
+ goto err_dmacreg;
+ }
+
+ /* tdmrd tmdtd at immrbar+0x16100 */
+ priv->data_regs =
+ (struct tdm_data *)(TDM_DATAREG_OFFSET + (u8 *)priv->tdm_regs);
+ /* TDMCLK_DIV_VAL_RX/TX at TDMBASE+0x180 */
+ priv->clk_regs =
+ (struct tdm_clock *)(TDM_CLKREG_OFFSET + (u8 *)priv->tdm_regs);
+
+ priv->dmac_done_intr = irq_of_parse_and_map(ofdev->dev.of_node, 0);
+ if (priv->dmac_done_intr == NO_IRQ) {
+ ret = -EINVAL;
+ goto err_dmacdone_irqmap;
+ }
+ ret =
+ request_irq(priv->dmac_done_intr, dmac_done_isr, 0, "dmac_done_isr",
+ priv);
+ if (ret)
+ goto err_dmacdoneisr;
+
+
+ priv->adap = &tdm_fsl_ops;
+
+ /* Wait q initilization */
+ priv->adap->tdm_rx_flag = 0;
+ /* todo - these should be configured by dts or init time */
+ tdm_set_adapdata(priv->adap, priv);
+ priv->adap->parent = &ofdev->dev;
+
+ ret = tdm_add_adapter(priv->adap);
+ if (ret < 0) {
+ dev_err(priv->device, "failed to add adapter\n");
+ goto fail_adapter;
+ }
+
+ /* Get tdm clk values from device tree */
+ tdm_tx_clk = (int *)of_get_property(np, "tdm_tx_clk", NULL);
+ if (!tdm_tx_clk) {
+ dev_err(priv->device, "Couldn't find tx clk\n");
+ goto no_tx_clk;
+ }
+ priv->adap->adapt_cfg.tdm_tx_clk = *tdm_tx_clk;
+
+ ret = init_tdm(priv);
+ if (ret)
+ goto err_tdminit;
+
+ ret = tdm_fsl_reg_init(priv);
+ if (ret)
+ goto err_tdminit;
+
+ p_num_phy = (int *)of_get_property(np, "num-phy", NULL);
+ if (!p_num_phy) {
+ dev_err(priv->device, "failed to get num-phy\n");
+ goto err_tdminit;
+ }
+ priv->adap->num_tdm_phy = *p_num_phy;
+ phandle_prop = (phandle *)of_get_property(np, "phy-handle", NULL);
+ priv->adap->phandle_prop = (void *)phandle_prop;
+ if (!phandle_prop) {
+ dev_err(priv->device, "failed to get phy-handle node\n");
+ goto err_get_phy_node;
+ }
+
+ for (i = 0; i < *p_num_phy; i++) {
+ ret = get_tdm_phy(priv->adap, ((int *)phandle_prop + i));
+ if (!ret)
+ dev_err(priv->device, "failed to get phy-handle\n");
+ }
+
+ spin_lock_init(&priv->tdmlock);
+ spin_lock(&priv->tdmlock);
+ priv->tdm_active = 0;
+ spin_unlock(&priv->tdmlock);
+
+ if (tdmen) {
+ ret = tdm_fsl_enable(priv->adap);
+ if (!ret)
+ goto err_tdminit;
+ }
+
+ return 0;
+
+err_get_phy_node:
+err_tdminit:
+no_tx_clk:
+fail_adapter:
+ free_irq(priv->dmac_done_intr, priv);
+err_dmacdoneisr:
+ free_irq(priv->tdm_err_intr, priv);
+err_dmacdone_irqmap:
+ irq_dispose_mapping(priv->dmac_done_intr);
+err_dmacreg:
+ iounmap(priv->dmac_regs);
+err_tdmregs:
+err_resource:
+ dev_set_drvdata(&ofdev->dev, NULL);
+ kfree(priv);
+err_alloc:
+ return ret;
+}
+
+static int tdm_fsl_remove(struct platform_device *ofdev)
+{
+ struct tdm_priv *priv;
+ int buf_size;
+ struct tdm_adapter *adap;
+
+ if (!ofdev) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ priv = dev_get_drvdata(&ofdev->dev);
+ adap = priv->adap;
+
+ tdm_fsl_disable(priv->adap);
+
+ tdm_fsl_stop(priv);
+
+ tdm_del_adapter(priv->adap);
+ dev_set_drvdata(&ofdev->dev, NULL);
+
+ /* free the irqs and dispose their mapping */
+ free_irq(priv->tdm_err_intr, priv);
+ free_irq(priv->dmac_done_intr, priv);
+ irq_dispose_mapping(priv->tdm_err_intr);
+ irq_dispose_mapping(priv->dmac_done_intr);
+ iounmap(priv->tdm_regs);
+ iounmap(priv->dmac_regs);
+
+ /* free the buffers */
+ buf_size =
+ TDM_BUF_SIZE(adap->adapt_cfg.num_ch, adap->adapt_cfg.slot_width,
+ adap->adapt_cfg.num_frames);
+ dma_free_coherent(priv->device, buf_size, priv->dma_input_vaddr,
+ priv->dma_input_paddr);
+ dma_free_coherent(priv->device, buf_size, priv->dma_output_vaddr,
+ priv->dma_output_paddr);
+
+ /* free the TCDs */
+ dma_free_coherent(priv->device, NUM_OF_TDM_BUF * TCD_BUFFER_SIZE,
+ priv->dma_rx_tcd_vaddr, priv->dma_rx_tcd_paddr);
+ dma_free_coherent(priv->device, NUM_OF_TDM_BUF * TCD_BUFFER_SIZE,
+ priv->dma_tx_tcd_vaddr, priv->dma_tx_tcd_paddr);
+ dev_set_drvdata(&ofdev->dev, NULL);
+ kfree(priv);
+ return 0;
+}
+
+static const struct of_device_id fsl_tdm_match[] = {
+ {
+ .compatible = "fsl,tdm1.0",
+ },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, fsl_tdm_match);
+
+static struct platform_driver tdm_fsl_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRV_NAME,
+ .of_match_table = fsl_tdm_match,
+
+ },
+ .probe = tdm_fsl_probe,
+ .remove = tdm_fsl_remove,
+};
+
+static int __init tdm_fsl_init(void)
+{
+ int ret;
+ pr_info(DRV_NAME ": " DRV_DESC ":Init\n");
+ ret = platform_driver_register(&tdm_fsl_driver);
+ if (ret)
+ pr_err(DRV_NAME
+ "of_register_platform_driver failed (%i)\n", ret);
+ return ret;
+}
+
+static void __exit tdm_fsl_exit(void)
+{
+ pr_info(DRV_NAME ": " DRV_DESC ":Exit\n");
+ platform_driver_unregister(&tdm_fsl_driver);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("P.V.Suresh, Freescale Semiconductor");
+MODULE_DESCRIPTION("Driver For Freescale TDM controller");
+MODULE_VERSION("1.1");
+
+module_init(tdm_fsl_init);
+module_exit(tdm_fsl_exit);
diff --git a/drivers/tdm/device/tdm_fsl.h b/drivers/tdm/device/tdm_fsl.h
new file mode 100644
index 0000000..a37f7c4
--- /dev/null
+++ b/drivers/tdm/device/tdm_fsl.h
@@ -0,0 +1,444 @@
+/*
+ * drivers/device/fsl_tdm.h
+ *
+ * Copyright (C) 2007-2012 Freescale Semiconductor, Inc, All rights reserved.
+ *
+ * Created by P. V. Suresh <pala@freescale.com>
+ *
+ * Modified by Rajesh Gumasta <rajesh.gumasta@freescale.com>
+ * 1. Modified to support MPC85xx based devices
+ * 2. Modified the priv structure to support Adapter Registeration
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef FSL_TDM_H
+#define FSL_TDM_H
+
+/* enable clock to TDM */
+#ifdef CONFIG_MPC831x_RDB
+
+#define SCCR_OFFSET 0x0A08
+#define SCCR_TDM_MASK 0x000000C0
+#define TDM_CM_01 (0x01<<6)
+
+/* enable tdm in SICR */
+#define SICRL_OFFSET 0x0114
+#define SICRL_TDM_MASK 0xF00F0000
+
+#endif
+
+/* TDM data register offset */
+#define TDM_TDR_OFFSET 0x108
+#define TDM_RDR_OFFSET 0x100
+#define TDM_DATAREG_OFFSET 0x100
+#define TDM_CLKREG_OFFSET 0x180
+
+/* max number of TDM-DMA channels */
+#define DMA_MAX_CHANNELS 4
+
+/* TCD params */
+#define SOFF_VAL 0x08
+#define DOFF_VAL 0x08
+#define NBYTES 0x08 /*Minor Bytes transfer count*/
+#define SLAST 0x00 /* last source addr adjustment*/
+#define SLAST_SGA 0x00
+#define DLAST_SGA 0x00
+
+/* RIR Params*/
+#define RIR_RFSD_VAL 0x01
+#define RIR_RFWM_VAL 0x00
+
+/* TIR Params*/
+#define TIR_RFSD_VAL 0x01
+#define TIR_RFWM_VAL 0x00
+
+/* TDMTCEN */
+#define NUM_TDMTCEN_REG 0x04
+#define TDMTCEN_REG_LEN 32
+
+/* each DMA-ch contains 8 Transfer Control Discriptors */
+#define DMA_MAX_TCD 8
+
+#define DMAC_TX_INT 1
+#define DMAC_RX_INT 2
+
+#define TDM_CHANNEL_8BIT_LIN 0x00000000 /* 8 bit linear */
+#define TDM_CHANNEL_8BIT_ULAW 0x00000001 /* 8 bit Mu-law */
+#define TDM_CHANNEL_8BIT_ALAW 0x00000002 /* 8 bit A-law */
+#define TDM_CHANNEL_16BIT_LIN 0x00000003 /* 16 bit Linear */
+
+/* DMAC TCD structure */
+struct tcd {
+ u32 tcd[DMA_MAX_TCD];
+};
+
+/* DMA Controllor */
+struct dmac_regs {
+ u32 dmacr; /* DMA Control Register */
+ u32 dmaes; /* DMA Error Status Register */
+ u32 dmaerqh; /* DMA Enable Request */
+ u32 dmaerql; /* DMA Enable Request */
+ u32 dmaeeih; /* DMA Enable Error Interrupt */
+ u32 dmaeeil; /* DMA Enable Error Interrupt */
+
+ u8 dmaserq; /* DMA Set Enable Request */
+ u8 dmacerq; /* DMA Clear Enable Request */
+ u8 dmaseei; /* DMA Set Enable Error Interrupt */
+ u8 dmaceei; /* DMA Clear Enable Error Interrupt */
+
+ u8 dmacint; /* DMA Clear Interrupt Request */
+ u8 dmacerr; /* DMA Clear Error */
+ u8 dmassrt; /* DMA Set Start Bit */
+ u8 dmacdne; /* DMA Clear Done Bit */
+
+ u32 dmainth; /* DMA Interrupt Request High */
+ u32 dmaintl; /* DMA Interrupt Request */
+ u32 dmaerrh; /* DMA Error */
+ u32 dmaerrl; /* DMA Error */
+ u32 dmahrsh; /* DMA Hardware Request status */
+ u32 dmahrsl; /* DMA HardWired Request status */
+ u32 dmagpor; /* DMA General Purpose Register */
+ u8 reserved0[0xC4];
+ u8 dchpri[DMA_MAX_CHANNELS]; /* DMA Port Priority */
+ u8 reserved1[0xEFC];
+ struct tcd tcd[DMA_MAX_CHANNELS]; /*Transfer Control Descriptor */
+};
+
+/* DMA GPOR */
+#define DMAGPOR_SNOOP 0x00000040 /* Enable Snooping */
+
+/* DMA Control Register (DMACR) */
+#define DMACR_EMLM 0x00000080 /* Enable Minor loop Mapping */
+#define DMACR_CLM 0x00000040 /* Continuous link mode */
+#define DMACR_HALT 0x00000020 /* Halt DMA */
+#define DMACR_HOE 0x00000010 /* Halt on Error */
+#define DMACR_ERGA 0x00000008 /* Round robin among the groups */
+#define DMACR_ERCA 0x00000004 /* Round robin Port Arbitration */
+#define DMACR_EDBG 0x00000002 /* Debug */
+#define DMACR_EBW 0x00000001 /* Enable Buffer */
+
+/* DMA Error Status DMAES */
+#define DMAES_VLD 0x80000000 /* Logical OR of all DMA errors. */
+#define DMAES_ECX 0x00010000 /* Transfer cancelled */
+#define DMAES_GPE 0x00008000 /* Group priority error */
+#define DMAES_CPE 0x00004000 /* Channel priority error */
+/* errored/cancelled channel */
+#define DMAES_ERRCHN(ERRCH) (((ERRCH) & 0x1F00) >> 8)
+#define DMAES_SAE 0x00000080 /* Source address error */
+#define DMAES_SOE 0x00000040 /* Source offset error */
+#define DMAES_DAE 0x00000020 /* Destination address error */
+#define DMAES_DOE 0x00000010 /* Destination offset error */
+#define DMAES_NCE 0x00000008 /* Nbytes citer error */
+#define DMAES_SGE 0x00000004 /* Scatter gather error */
+#define DMAES_SBE 0x00000002 /* Source bus error */
+#define DMAES_DBE 0x00000001 /* Destination bus error */
+
+/* DMA Enable Request (DMAERQH, DMAERQL) Enable/disable device
+ request for the channel */
+#define DMA_SET_ENABLE_REQUEST(REGS, CH) out_8(((REGS)->dmasreq), CH)
+#define DMA_CLEAR_ENABLE_REQUEST(REGS, CH) out_8(((REGS)->dmacerq), CH)
+
+/* DMA Enable Error Interrupt (DMAEEIH, DMAEEIL) Enable/disable
+ error interrupt for the channel */
+#define DMA_SET_ENABLE_ERROR_INT(REGS, CH) out_8(((REGS)->dmaseei), CH)
+#define DMA_CLEAR_ENABLE_ERROR_INT(REGS, CH) out_8(((REGS)->dmaceei), CH)
+
+/* Clear interrupt/error for the channel */
+#define DMA_CLEAR_INTT_REQUEST(REGS, CH) out_8(((REGS)->dmacint), CH)
+#define DMA_CLEAR_ERROR(REGS, CH) out_8(((REGS)->dmacerr), CH)
+
+/* Clear done bit for the channel */
+#define DMA_CLEAR_DONE_BIT(REGS, CH) out_8(((REGS)->dmacdne), CH)
+/* Set start bit for the channel */
+#define DMA_SET_START_BIT(REGS, CH) out_8(((REGS)->dmassrt), CH)
+
+#define TDMTX_DMA_CH 0 /* TDM Tx uses DMA 0 HardWired */
+#define TDMRX_DMA_CH 1 /* TDM Rx uses DMA 1 Hardwired */
+#define TCD_SIZE 32 /* 32 byte TCD for channel */
+#define TCD_BUFFER_SIZE 64 /* 64 byte buffer for TCD */
+
+/* Source address modulo */
+#define DMA_TCD1_SMOD(SMOD) (((SMOD) & 0x1F) << 27)
+/* Source data transfer size */
+#define DMA_TCD1_SSIZE(SSIZE) (((SSIZE) & 0x7) << 24)
+
+/* Destination address modulo */
+#define DMA_TCD1_DMOD(DMOD) (((DMOD) & 0x1F) << 19)
+/* data transfer size */
+#define DMA_TCD1_DSIZE(DSIZE) (((DSIZE) & 0x7) << 16)
+
+/* Source address signed offset */
+#define DMA_TCD1_SOFF(SOFF) ((SOFF) & 0xFFFF)
+
+/* Enable link to another channel on minor iteration completion. */
+#define DMA_TCD5_E_MINOR_LINK 0x80000000
+/* Link to this channel. */
+#define DMA_TCD5_LINK_CH(CH) (((CH) & 0x3F) << 25)
+/* Current iteration count when linking disnabled */
+#define DMA_TCD5_CITER_DISABLE_LINK(CITER) (((CITER) & 0x7FFF) << 16)
+/* Current iteration count when linking enabled */
+#define DMA_TCD5_CITER_ENABLE_LINK(CITER) (((CITER) & 0x00FF) << 16)
+/* Destination address signed offset */
+#define DMA_TCD5_DOFF(DOFF) ((DOFF) & 0xFFFF)
+
+/* Beginning iteration count when linking disnabled */
+#define DMA_TCD7_BITER_DISABLE_LINK(CITER) (((CITER) & 0x7FFF) << 16)
+/* Beginning iteration count when linking enabled */
+#define DMA_TCD7_BITER_ENABLE_LINK(CITER) (((CITER) & 0x00FF) << 16)
+#define DMA_TCD7_BWC(BW) (((BW)&0x3)<<14) /* BandWidth Control. */
+/* Link channel number */
+#define DMA_TCD7_LINKCH(CH) (((CH) & 0x1F) << 8)
+#define DMA_TCD7_DONE 0x00000080 /* Channel done */
+#define DMA_TCD7_ACTIVE 0x00000040 /* Channel active */
+#define DMA_TCD7_E_MAJOR_LINK 0x00000020 /* channel to channel linking */
+#define DMA_TCD7_E_SG 0x00000010 /* Enable scatter gather */
+#define DMA_TCD7_D_REQ 0x00000008 /* Disable request */
+/* interrupt on half major counter */
+#define DMA_TCD7_INT_HALF 0x00000004
+#define DMA_TCD7_INT_MAJ 0x00000002 /* interrupt on major counter */
+#define DMA_TCD7_START 0x00000001 /* Channel start */
+
+#define SSIZE_08BITS 0x00 /* port 1 byte */
+#define SSIZE_16BITS 0x01
+#define SSIZE_32BITS 0x02
+#define SSIZE_64BITS 0x03
+
+/* TDM Control Registers. */
+struct tdm_regs {
+ u32 gir; /* General Interface Register */
+ u32 rir; /* Receive Interface Register */
+ u32 tir; /* Transmit Interface Register */
+ u32 rfp; /* Receive Frame Parameters */
+ u32 tfp; /* Transmit Frame Parameters */
+ u8 reserved0[0xC];
+ u32 rcen[4]; /* Recieve Channel Enabled */
+ u8 reserved1[0x10];
+ u32 tcen[4]; /* Transmit Channel Enabled */
+ u8 reservedd2[0x10];
+ u32 tcma[4]; /* Transmit Channel Mask */
+ u8 reservederved3[0x10];
+ u32 rcr; /* Receiver Control Register */
+ u32 tcr; /* Transmitter Control Register */
+ u32 rier; /* Receive Interrupt Enable Register */
+ u32 tier; /* Transmit Interrupt Enable Register */
+ u8 reserved4[0x10];
+ u32 rer; /* Receive Event Register */
+ u32 ter; /* Transmit Event Register */
+ u32 rsr; /* Receive Status Register */
+ u32 tsr; /* Transmit Status Register */
+};
+
+struct tdm_data {
+ u64 rdr; /* Receive Data Register */
+ u64 tdr; /* Transmit Dataa Register */
+};
+
+struct tdm_clock {
+ u32 rx; /* Transmit Dataa Register */
+ u32 tx; /* Receive Data Register */
+};
+
+/* TDMGIR General Interface Register */
+#define GIR_LPBK 0x00000004 /* loopback mode */
+#define GIR_CTS 0x00000002 /* Common TDM signals */
+#define GIR_RTS 0x00000001 /* Rx & Tx sharing */
+
+/* TDMRIR Recieve Interface Rgister */
+#define RIR_RFWM_MASK 0x00000003 /* Recieve FIFO Watermark */
+#define RIR_RFWM_SHIFT 16
+#define RIR_RFWM(x) ((x & RIR_RFWM_MASK) << RIR_RFWM_SHIFT)
+#define RIR_RFEN 0x00008000 /* Recieve FIFO Enable */
+#define RIR_RWEN 0x00004000 /* Recieve Wide FIFO Enable */
+#define RIR_RDMA 0x00000040 /* Recieve DMA Enable */
+#define RIR_RFSD_SHIFT 0x00000004 /* Recieve Frame Sync Delay */
+#define RIR_RFSD_MASK 0x00000003
+#define RIR_RFSD(x) ((x & RIR_RFSD_MASK) << RIR_RFSD_SHIFT)
+#define RIR_RSO 0x00002000 /* Recieve sync Out */
+#define RIR_RSL 0x00000800 /* Recieve sync Out Length */
+#define RIR_RSOE 0x00000400 /* Recieve sync Out Edge */
+#define RIR_RCOE 0x00000200 /* Recieve Clock Output Enable */
+#define RIR_RSA 0x00000008 /* Recieve Sync Active */
+#define RIR_RDE 0x00000004 /* Recieve Data Edge */
+#define RIR_RFSE 0x00000002 /* Recieve Frame Sync Edge */
+#define RIR_RRDO 0x00000001 /* Revieve Reversed Data Order */
+
+/* TDMTIR Transmit Interface Rgister */
+#define TIR_TFWM_MASK 0x00000003 /* Transmit FIFO Watermark */
+#define TIR_TFWM_SHIFT 16
+#define TIR_TFWM(x) ((x & TIR_TFWM_MASK) << TIR_TFWM_SHIFT)
+#define TIR_TFEN 0x00008000 /* Transmit FIFO Enable */
+#define TIR_TWEN 0x00004000 /* Transmit Wide FIFO Enable */
+#define TIR_TDMA 0x00000040 /* Transmit DMA Enable */
+#define TIR_TFSD_SHIFT 0x00000004 /* Transmit Frame Sync Delay */
+#define TIR_TFSD_MASK 0x00000003
+#define TIR_TFSD(x) ((x & TIR_TFSD_MASK) << TIR_TFSD_SHIFT)
+#define TIR_TSO 0x00002000 /* Transmit Sync Output */
+#define TIR_TSL 0x00000800 /* Transmit sync Out Length */
+#define TIR_TSOE 0x00000400 /* Transmit sync Out Edge */
+#define TIR_TCOE 0x00000200 /* Transmit Clock Output Enable */
+#define TIR_TSA 0x00000008 /* Transmit Sync Active */
+#define TIR_TDE 0x00000004 /* Transmit Data Edge */
+#define TIR_TFSE 0x00000002 /* Transmit Frame Sync Edge */
+#define TIR_TRDO 0x00000001 /* Transmit Reversed Data Order */
+
+/*TDMRFP Revieve Frame Parameters */
+#define RFP_RNCF_SHIFT 0x00000010 /* Number of Channels in TDM Frame */
+#define RFP_RNCF_MASK 0x000000FF
+#define RFP_RNCF(x) (((x - 1) & RFP_RNCF_MASK) << RFP_RNCF_SHIFT)
+#define RFP_RCS_SHIFT 0x00000004 /* Recieve Channel Size */
+#define RFP_RCS_MASK 0x00000003
+#define RFP_RCS(x) ((x & RFP_RCS_MASK) << RFP_RCS_SHIFT)
+#define RFP_RT1 0x00000002 /* Recieve T1 Frame */
+
+/*TDMTFP Transmit Frame Parameters */
+#define TFP_TNCF_SHIFT 0x00000010 /* Number of Channels in TDM Frame */
+#define TFP_TNCF_MASK 0x000000FF
+#define TFP_TNCF(x) (((x - 1) & TFP_TNCF_MASK) << TFP_TNCF_SHIFT)
+#define TFP_TCS_SHIFT 0x00000004 /* Transmit Channel Size */
+#define TFP_TCS_MASK 0x00000003
+#define TFP_TCS(x) ((x & RFP_RCS_MASK) << RFP_RCS_SHIFT)
+#define TFP_TT1 0x00000002 /* Transmit T1 Frame */
+
+
+/* TDMRCR Recieve Control Register */
+#define RCR_REN 0x00000001 /* Recieve Enable */
+/* TDMTCR Transmit Control Register */
+#define TCR_TEN 0x00000001 /* Transmit Enable */
+
+/* TDMRIER receive interrupt enable register */
+#define RIER_RCEUE 0x00000100 /* Channel Enable Update Enable */
+#define RIER_RLCEE 0x00000080 /* Recieve Last Channel Event Enable */
+#define RIER_RFSEE 0x00000040 /* Recieve Frame Sync Event Enable */
+#define RIER_RFFEE 0x00000020 /* Recieve FIFO Full Event Enable */
+#define RIER_RDREE 0x00000010 /* Recieve Data Ready Event Enable */
+#define RIER_RSEEE 0x00000008 /* Recieve Sync Error Event Enable */
+#define RIER_ROEE 0x00000004 /* Recieve Overrun Event Enable */
+
+/* TDMTIER transmit interrupt enable register */
+#define TIER_TCEUE 0x00000100 /* Channel Enable Update Enable */
+#define TIER_TLCEE 0x00000080 /* Transmit Last Channel Event */
+#define TIER_TFSEE 0x00000040 /* Transmit Frame Sync Event Enable */
+#define TIER_TFFEE 0x00000020 /* Transmit FIFO Full Event Enable */
+#define TIER_TDREE 0x00000010 /* Transmit Data Ready Event Enable */
+#define TIER_TSEEE 0x00000008 /* Transmit Sync Error Event Enable */
+#define TIER_TUEE 0x00000004 /* Transmit Overrun Event Enable */
+
+/* TDMRER Recieve Event Register */
+#define RER_RCEU 0x00000100 /* Recieve Channel Enable Update */
+#define RER_RLCE 0x00000080 /* Recieve Last Channel Event */
+#define RER_RFSE 0x00000040 /* Recieve Frame Sync Event */
+#define RER_RFFE 0x00000020 /* Recieve FIFO Full Event */
+#define RER_RDRE 0x00000010 /* Recieve Data Ready Event */
+#define RER_RSEE 0x00000008 /* Recieve Sync Error Event */
+#define RER_ROE 0x00000004 /* Recieve Overrun Event */
+
+/* TDMTER Transmit Event Register */
+#define TER_TCEU 0x00000100 /* Transmit Channel Enable Update */
+#define TER_TLCE 0x00000080 /* Transmit Last Channel Event */
+#define TER_TFSE 0x00000040 /* Transmit Frame Sync Event */
+#define TER_TFEE 0x00000020 /* Transmit FIFO Full Event */
+#define TER_TDRE 0x00000010 /* Transmit Data Ready Event */
+#define TER_TSEE 0x00000008 /* Transmit Sync Error Event */
+#define TER_TUE 0x00000004 /* Transmit Overrun Event */
+
+/* TDMRSR Recieve Status Register */
+#define RSR_RFCNT 0x00000038 /* Recieve FIFO counter */
+#define RSSS_MASK 0x00000003 /* Recieve SYNC Status */
+#define RSR_RSSS_SHIFT 1
+#define RSR_RSSS(SSS) (((SSS) >> (RSR_RSSS_SHIFT)) & (RSR_RSSS_MASK))
+#define RSR_RENS 0x00000001 /* Recieve Enable Status */
+
+/* TDMTSR Transmit Status Register */
+#define TSR_TFCNT 0x00000038 /* Transmit FIFO counter */
+#define TSR_TSSS_MASK 0x00000003 /* Transmit SYNC Status */
+#define TSR_TSSS_SHIFT 1
+#define TSR_TSSS(SSS) (((SSS) >> (TSR_TSSS_SHIFT)) & TSR_TSSS_MASK)
+#define TSR_TENS 0x00000001 /* Transmit Enable Status */
+
+/* channel width */
+#define CHANNEL_SIZE_1BYTE 1 /* 1 byte channel 8-bit linear */
+#define CHANNEL_SIZE_2BYTE 2 /* 2 bytes */
+
+/* channel parameters */
+#define TDM_ENABLE_TIMEOUT 1000 /* time out for TDM rx, tx enable */
+#define NUM_OF_TDM_BUF 3 /* Number of tdm buffers for startlite
+ DMA */
+#define ALIGNED_2_BYTES 0x02 /* 2-bytes alignment */
+#define ALIGNED_4_BYTES 0x04 /* 4-bytes alignment */
+#define ALIGNED_8_BYTES 0x08 /* 8-bytes alignment */
+#define ALIGNED_16_BYTES 0x10 /* 16-bytes alignment */
+#define ALIGNED_32_BYTES 0x20 /* 32-bytes alignment */
+#define ALIGNED_64_BYTES 0x40 /* 64-bytes alignment */
+
+/* Extend a given size to make it alignable */
+static inline int ALIGNABLE_SIZE(u32 size, u32 alignment)
+{
+ return size + alignment - 1;
+}
+
+/* Align a given address */
+static inline void *ALIGN_ADDRESS(void *address, u32 alignment)
+{
+ return (void *)(((u32) address + alignment - 1) & (~(alignment - 1)));
+}
+
+/* Size of the buffer */
+static inline int TDM_1BUF_SIZE(u32 num_ch, u32 channel_size, u32 frame_size)
+{
+ return frame_size *
+ ALIGN_SIZE(channel_size * num_ch, ALIGNED_8_BYTES);
+}
+
+/* Alignable size of the 3 buffers */
+static inline int TDM_BUF_SIZE(u32 num_ch, u32 channel_size, u32 frame_size)
+{
+ return
+ ALIGNABLE_SIZE((TDM_1BUF_SIZE(num_ch, channel_size, frame_size) *
+ NUM_OF_TDM_BUF), ALIGNED_8_BYTES);
+}
+
+
+struct tdm_priv {
+ struct tdm_regs __iomem *tdm_regs;
+ struct tdm_data __iomem *data_regs;
+ struct dmac_regs __iomem *dmac_regs;
+ struct tdm_clock __iomem *clk_regs;
+ u32 ptdm_base;
+ u8 *tdm_input_data;
+ u8 *tdm_output_data;
+ dma_addr_t dma_input_paddr; /* dma mapped buffer for TDM Rx */
+ dma_addr_t dma_output_paddr; /* dma mapped buffer for TDM Tx */
+ void *dma_input_vaddr;
+ void *dma_output_vaddr;
+ u32 phase_rx;
+ u32 phase_tx;
+ struct tcd *dma_rx_tcd[NUM_OF_TDM_BUF];
+ struct tcd *dma_tx_tcd[NUM_OF_TDM_BUF];
+ dma_addr_t dma_rx_tcd_paddr;
+ dma_addr_t dma_tx_tcd_paddr;
+ void *dma_rx_tcd_vaddr;
+ void *dma_tx_tcd_vaddr;
+ u32 tdm_buffer_size;
+ u32 tdm_err_intr;
+ u32 dmac_err_intr;
+ u32 dmac_done_intr;
+ int tdm_active;
+ struct device *device;
+ spinlock_t tdmlock;
+ struct tdm_adapter *adap;
+};
+
+#endif
--
1.7.6.GIT
^ permalink raw reply related
* [PATCH 2/4][v2] TDM Framework
From: Sandeep Singh @ 2013-03-05 12:41 UTC (permalink / raw)
To: linuxppc-dev; +Cc: Sandeep Singh, Poonam Aggrwal, linux-arm-kernel
In-Reply-To: <1362487297-19702-1-git-send-email-Sandeep@freescale.com>
TDM Framework is an attempt to provide a platform
independent layer which can offer a standard interface
for TDM access to different client modules. Beneath,
the framework layer can house different types of TDM
drivers to handle various TDM devices, the hardware
intricacies of the devices being completely taken care
by TDM drivers.
This framework layer will allow any type of TDM device to hook with it.
For example Freescale controller as on MPC8315, UCC based TDM controller, etc
The main functions of this Framework are:
- provides interface to TDM clients to access TDM functionalities.
- provides standard interface for TDM drivers to hook with the framework.
- provides standard interface for line control devices to hook with the framework.
- handles various data handling stuff and buffer management.
Presently the framework supports only Single Port channelised mode.
Also the configurability options are limited which will be extended later on.
Only kernel mode TDM clients are supported currently. Support for User mode clients will be added later.
Signed-off-by: Sandeep Singh <Sandeep@freescale.com>
Signed-off-by: Poonam Aggrwal <poonam.aggrwal@freescale.com>
---
drivers/Kconfig | 1 +
drivers/Makefile | 1 +
drivers/tdm/Kconfig | 25 +
drivers/tdm/Makefile | 8 +
drivers/tdm/tdm-core.c | 1198 +++++++++++++++++++++++++++++++++++++++
include/linux/mod_devicetable.h | 11 +
include/linux/tdm.h | 379 +++++++++++++
7 files changed, 1623 insertions(+), 0 deletions(-)
create mode 100644 drivers/tdm/Kconfig
create mode 100644 drivers/tdm/Makefile
create mode 100644 drivers/tdm/tdm-core.c
create mode 100644 include/linux/tdm.h
diff --git a/drivers/Kconfig b/drivers/Kconfig
index f5fb072..56a7707 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -158,4 +158,5 @@ source "drivers/irqchip/Kconfig"
source "drivers/ipack/Kconfig"
+source "drivers/tdm/Kconfig"
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 7863b9f..2f8f130 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -112,6 +112,7 @@ obj-$(CONFIG_INFINIBAND) += infiniband/
obj-$(CONFIG_SGI_SN) += sn/
obj-y += firmware/
obj-$(CONFIG_CRYPTO) += crypto/
+obj-$(CONFIG_TDM) += tdm/
obj-$(CONFIG_SUPERH) += sh/
obj-$(CONFIG_ARCH_SHMOBILE) += sh/
ifndef CONFIG_ARCH_USES_GETTIMEOFFSET
diff --git a/drivers/tdm/Kconfig b/drivers/tdm/Kconfig
new file mode 100644
index 0000000..8db2b05
--- /dev/null
+++ b/drivers/tdm/Kconfig
@@ -0,0 +1,25 @@
+#
+# TDM subsystem configuration
+#
+
+menuconfig TDM
+ tristate "TDM support"
+ ---help---
+ More information is contained in the directory <file:Documentation/tdm/>,
+ especially in the file called "summary" there.
+ If you want TDM support, you should say Y here and also to the
+ specific driver for your bus adapter(s) below.
+
+ This TDM support can also be built as a module. If so, the module
+ will be called tdm-core.
+
+if TDM
+
+config TDM_DEBUG_CORE
+ bool "TDM Core debugging messages"
+ help
+ Say Y here if you want the TDM core to produce a bunch of debug
+ messages to the system log. Select this if you are having a
+ problem with TDM support and want to see more of what is going on.
+
+endif # TDM
diff --git a/drivers/tdm/Makefile b/drivers/tdm/Makefile
new file mode 100644
index 0000000..5569616
--- /dev/null
+++ b/drivers/tdm/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for the TDM core.
+#
+
+obj-$(CONFIG_TDM) += tdm-core.o
+ifeq ($(CONFIG_TDM_DEBUG_CORE),y)
+EXTRA_CFLAGS += -DDEBUG
+endif
diff --git a/drivers/tdm/tdm-core.c b/drivers/tdm/tdm-core.c
new file mode 100644
index 0000000..fb3d6b9
--- /dev/null
+++ b/drivers/tdm/tdm-core.c
@@ -0,0 +1,1198 @@
+/* driver/tdm/tdm-core.c
+ *
+ * Copyright (C) 2012 Freescale Semiconductor, Inc, All rights reserved.
+ *
+ * TDM core is the interface between TDM clients and TDM devices.
+ * It is also intended to serve as an interface for line controld
+ * devices later on.
+ *
+ * Author:Hemant Agrawal <hemant@freescale.com>
+ * Rajesh Gumasta <rajesh.gumasta@freescale.com>
+ *
+ * Modified by Sandeep Kr Singh <sandeep@freescale.com>
+ * Poonam Aggarwal <poonam.aggarwal@freescale.com>
+ * 1. Added framework based initilization of device.
+ * 2. All the init/run time configuration is now done by framework.
+ * 3. Added channel level operations.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* if read write debug required */
+#undef TDM_CORE_DEBUG
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tdm.h>
+#include <linux/init.h>
+#include <linux/idr.h>
+#include <linux/mutex.h>
+#include <linux/completion.h>
+#include <linux/hardirq.h>
+#include <linux/irqflags.h>
+#include <linux/list.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+
+static DEFINE_MUTEX(tdm_core_lock);
+static DEFINE_MUTEX(tdm_adapter_lock);
+static DEFINE_IDR(tdm_adapter_idr);
+/* List of TDM adapters registered with TDM framework */
+LIST_HEAD(tdm_adapter_list);
+
+/* List of TDM phys registered with TDM framework */
+LIST_HEAD(tdm_phy_list);
+
+/* List of TDM clients registered with TDM framework */
+LIST_HEAD(tdm_driver_list);
+
+/* In case the previous data is not fetched by the client driver, the
+ * de-interleaving function will discard the old data and rewrite the
+ * new data */
+static int use_latest_tdm_data = 1;
+
+/* this tasklet is created for each adapter instance */
+static void tdm_data_tasklet_fn(unsigned long);
+
+/* tries to match client driver with the adapter */
+static int tdm_device_match(struct tdm_driver *driver, struct tdm_adapter *adap)
+{
+ /* match on an id table if there is one */
+ if (driver->id_table && driver->id_table->name[0]) {
+ if (!(strcmp(driver->id_table->name, adap->name)))
+ return (int)driver->id_table;
+ }
+ return TDM_E_OK;
+}
+
+static int tdm_attach_driver_adap(struct tdm_driver *driver,
+ struct tdm_adapter *adap)
+{
+ int ret = TDM_E_OK;
+ /* if driver is already attached to any other adapter, return*/
+ if (driver->adapter && (driver->adapter != adap))
+ return ret;
+
+ driver->adapter = adap;
+
+ if (driver->attach_adapter) {
+ ret = driver->attach_adapter(adap);
+ if (ret < 0) {
+ pr_err("attach_adapter failed for driver [%s] err:%d\n"
+ , driver->name, ret);
+ return ret;
+ }
+ }
+ adap->drv_count++;
+
+ if (!adap->tasklet_conf) {
+ tasklet_init(&adap->tdm_data_tasklet, tdm_data_tasklet_fn,
+ (unsigned long)adap);
+ adap->tasklet_conf = 1;
+ }
+
+ return ret;
+}
+
+/* Detach client driver and adapter */
+static int tdm_detach_driver_adap(struct tdm_driver *driver,
+ struct tdm_adapter *adap)
+{
+ int res = TDM_E_OK;
+
+ if (!driver->adapter || (driver->adapter != adap))
+ return TDM_E_OK;
+
+ if (!driver->detach_adapter)
+ return TDM_E_OK;
+
+ adap->drv_count--;
+
+ /* If no more driver is registed with the adapter*/
+ if (!adap->drv_count && adap->tasklet_conf) {
+ tasklet_disable(&adap->tdm_data_tasklet);
+ tasklet_kill(&adap->tdm_data_tasklet);
+ adap->tasklet_conf = 0;
+ }
+
+ if (driver->detach_adapter) {
+ if (driver->detach_adapter(adap))
+ pr_err("detach_adapter failed for driver [%s]\n",
+ driver->name);
+ }
+
+ driver->adapter = NULL;
+ return res;
+}
+
+/* TDM adapter Registration/De-registration with TDM framework */
+
+static int tdm_register_adapter(struct tdm_adapter *adap)
+{
+ int res = TDM_E_OK;
+ struct tdm_driver *driver, *next;
+
+ mutex_init(&adap->adap_lock);
+ INIT_LIST_HEAD(&adap->myports);
+ spin_lock_init(&adap->portlist_lock);
+
+ adap->drv_count = 0;
+ adap->tasklet_conf = 0;
+
+ list_add_tail(&adap->list, &tdm_adapter_list);
+
+ /* initialization of driver by framework in default configuration */
+ init_config_adapter(adap);
+
+ /* Notify drivers */
+ pr_info("adapter [%s] registered\n", adap->name);
+ mutex_lock(&tdm_core_lock);
+ list_for_each_entry_safe(driver, next, &tdm_driver_list, list) {
+ if (tdm_device_match(driver, adap)) {
+ res = tdm_attach_driver_adap(driver, adap);
+ if (res == TDM_E_OK) {
+ pr_info("Driver(ID=%d) is "
+ "attached with Adapter %s(ID = %d)\n",
+ driver->id, adap->name, adap->id);
+ } else {
+ pr_err("Driver(ID=%d) is unable "
+ "to attach with Adapter %s(ID = %d)\n",
+ driver->id, adap->name, adap->id);
+ }
+ }
+ }
+ mutex_unlock(&tdm_core_lock);
+
+ return res;
+}
+
+/*
+ * tdm_add_adapter - declare tdm adapter, use dynamic device number
+ * @adapter: the adapter to add
+ * Context: can sleep
+ *
+ * This routine is used to declare a TDM adapter
+ * When this returns zero, a new device number will be allocated and stored
+ * in adap->id, and the specified adapter became available for the clients.
+ * Otherwise, a negative errno value is returned.
+ */
+int tdm_add_adapter(struct tdm_adapter *adapter)
+{
+ int id, res = TDM_E_OK;
+ if (!adapter) {
+ pr_err("%s:Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+retry:
+ if (idr_pre_get(&tdm_adapter_idr, GFP_KERNEL) == 0)
+ return -ENOMEM;
+
+ mutex_lock(&tdm_core_lock);
+ res = idr_get_new(&tdm_adapter_idr, adapter, &id);
+ mutex_unlock(&tdm_core_lock);
+
+ if (res < 0) {
+ if (res == -EAGAIN)
+ goto retry;
+ return res;
+ }
+
+ adapter->id = id;
+ return tdm_register_adapter(adapter);
+}
+EXPORT_SYMBOL(tdm_add_adapter);
+
+/**
+ * tdm_del_adapter - unregister TDM adapter
+ * @adap: the adapter being unregistered
+ *
+ * This unregisters an TDM adapter which was previously registered
+ * by @tdm_add_adapter.
+ */
+int tdm_del_adapter(struct tdm_adapter *adap)
+{
+ int res = TDM_E_OK;
+ struct tdm_adapter *found;
+ struct tdm_driver *driver, *next;
+
+ if (!adap) {
+ pr_err("%s:Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ /* First make sure that this adapter was ever added */
+ mutex_lock(&tdm_core_lock);
+ found = idr_find(&tdm_adapter_idr, adap->id);
+ mutex_unlock(&tdm_core_lock);
+ if (found != adap) {
+ pr_err("tdm-core: attempting to delete unregistered "
+ "adapter [%s]\n", adap->name);
+ return -EINVAL;
+ }
+
+ /*disable and kill the data processing tasklet */
+ if (adap->tasklet_conf) {
+ tasklet_disable(&adap->tdm_data_tasklet);
+ tasklet_kill(&adap->tdm_data_tasklet);
+ adap->tasklet_conf = 0;
+ }
+
+ /* Detach any active ports. This can't fail, thus we do not
+ checking the returned value. */
+ mutex_lock(&tdm_core_lock);
+ list_for_each_entry_safe(driver, next, &tdm_driver_list, list) {
+ if (tdm_device_match(driver, adap)) {
+ tdm_detach_driver_adap(driver, adap);
+ pr_info(
+ "Driver(ID=%d) is detached from Adapter %s(ID = %d)\n",
+ driver->id, adap->name, adap->id);
+ }
+ }
+ mutex_unlock(&tdm_core_lock);
+
+ mutex_lock(&tdm_core_lock);
+ idr_remove(&tdm_adapter_idr, adap->id);
+ mutex_unlock(&tdm_core_lock);
+
+ pr_debug("adapter [%s] unregistered\n", adap->name);
+
+ list_del(&adap->list);
+ /* Clear the device structure in case this adapter is ever going to be
+ added again */
+ adap->parent = NULL;
+
+ return res;
+}
+EXPORT_SYMBOL(tdm_del_adapter);
+
+/* TDM Client Drivers Registration/De-registration Functions */
+int tdm_register_driver(struct tdm_driver *driver)
+{
+ int res = TDM_E_OK;
+ struct tdm_adapter *adap, *next;
+
+ list_add_tail(&driver->list, &tdm_driver_list);
+
+ mutex_lock(&tdm_core_lock);
+ /* Walk the adapters that are already present */
+ list_for_each_entry_safe(adap, next, &tdm_adapter_list, list) {
+ if (tdm_device_match(driver, adap)) {
+ res = tdm_attach_driver_adap(driver, adap);
+ if (res == TDM_E_OK) {
+ pr_info("TDM Driver(ID=%d)is attached with "
+ "Adapter%s(ID = %d) drv_count=%d",
+ driver->id, adap->name, adap->id,
+ adap->drv_count);
+ } else {
+ pr_err("TDM Driver(ID=%d) unable to attach "
+ "to Adapter%s(ID = %d) drv_count=%d",
+ driver->id, adap->name, adap->id,
+ adap->drv_count);
+ }
+ break;
+ }
+ }
+ mutex_unlock(&tdm_core_lock);
+
+ return res;
+}
+EXPORT_SYMBOL(tdm_register_driver);
+
+/*
+ * tdm_unregister_driver - unregister TDM client driver from TDM framework
+ * @driver: the driver being unregistered
+ */
+void tdm_unregister_driver(struct tdm_driver *driver)
+{
+ if (!driver) {
+ pr_err("%s:Invalid handle\n", __func__);
+ return;
+ }
+ /* A driver can register to only one adapter,
+ * so no need to browse the list */
+ mutex_lock(&tdm_core_lock);
+ tdm_detach_driver_adap(driver, driver->adapter);
+ mutex_unlock(&tdm_core_lock);
+
+ list_del(&driver->list);
+
+ pr_debug("tdm-core: driver [%s] unregistered\n", driver->name);
+}
+EXPORT_SYMBOL(tdm_unregister_driver);
+
+/* TDM Framework init and exit */
+static int __init tdm_init(void)
+{
+ pr_info("%s\n", __func__);
+ return TDM_E_OK;
+}
+
+static void __exit tdm_exit(void)
+{
+ pr_info("%s\n", __func__);
+ return;
+}
+
+/* We must initialize early, because some subsystems register tdm drivers
+ * in subsys_initcall() code, but are linked (and initialized) before tdm.
+ */
+postcore_initcall(tdm_init);
+module_exit(tdm_exit);
+
+
+/* Interface to the tdm device/adapter */
+
+/* tdm_adap_send - issue a TDM write
+ * @adap: Handle to TDM device
+ * @buf: Data that will be written to the TDM device
+ * @count: How many bytes to write
+ *
+ * Returns negative errno, or else the number of bytes written.
+ */
+int tdm_adap_send(struct tdm_adapter *adap, void **buf, int count)
+{
+ int res;
+
+ if ((adap == NULL) || (buf == NULL)) { /* invalid handle*/
+ pr_err("%s: Invalid Handle\n", __func__);
+ return -ENXIO;
+ }
+
+ if (adap->algo->tdm_write)
+ res = adap->algo->tdm_write(adap, buf, count);
+ else {
+ pr_err("TDM level write not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ /* If everything went ok (i.e. frame transmitted), return #bytes
+ transmitted, else error code. */
+ return (res == 1) ? count : res;
+}
+EXPORT_SYMBOL(tdm_adap_send);
+
+/**
+ * tdm_adap_recv - issue a TDM read
+ * @adap: Handle to TDM device
+ * @buf: Where to store data read from TDM device
+ *
+ * Returns negative errno, or else the number of bytes read.
+ */
+int tdm_adap_recv(struct tdm_adapter *adap, void **buf)
+{
+ int res;
+
+ if (adap->algo->tdm_read)
+ res = adap->algo->tdm_read(adap, (u16 **)buf);
+ else {
+ pr_err("TDM level read not supported\n");
+ return -EOPNOTSUPP;
+ }
+ /* If everything went ok (i.e. frame received), return #bytes
+ transmitted, else error code. */
+ return res;
+}
+
+/**
+ * tdm_adap_get_write_buf - get next write TDM device buffer
+ * @adap: Handle to TDM device
+ * @buf: pointer to TDM device buffer
+ *
+ * Returns negative errno, or else size of the write buffer.
+ */
+int tdm_adap_get_write_buf(struct tdm_adapter *adap, void **buf)
+{
+ int res;
+
+ if (adap->algo->tdm_get_write_buf) {
+ res = adap->algo->tdm_get_write_buf(adap, (u16 **)buf);
+ } else {
+ pr_err("TDM level write buf get not supported\n");
+ return -EOPNOTSUPP;
+ }
+ /* If everything went ok (i.e. 1 msg received), return #bytes
+ transmitted, else error code. */
+ return res;
+}
+EXPORT_SYMBOL(tdm_adap_get_write_buf);
+
+int tdm_adap_enable(struct tdm_driver *drv)
+{
+ int res;
+ struct tdm_adapter *adap;
+ if (drv == NULL) { /* invalid handle*/
+ pr_err("%s: Invalid Handle\n", __func__);
+ return -ENXIO;
+ }
+ adap = drv->adapter;
+
+ if (adap->algo->tdm_enable) {
+ res = adap->algo->tdm_enable(adap);
+ } else {
+ pr_err("TDM level enable not supported\n");
+ return -EOPNOTSUPP;
+ }
+ return res;
+}
+EXPORT_SYMBOL(tdm_adap_enable);
+
+int tdm_adap_disable(struct tdm_driver *drv)
+{
+ int res;
+ struct tdm_adapter *adap;
+ if (drv == NULL) { /* invalid handle*/
+ pr_err("%s: Invalid Handle\n", __func__);
+ return -ENXIO;
+ }
+ adap = drv->adapter;
+
+ if (adap->algo->tdm_disable) {
+ res = adap->algo->tdm_disable(adap);
+ } else {
+ pr_err("TDM level enable not supported\n");
+ return -EOPNOTSUPP;
+ }
+ return res;
+}
+EXPORT_SYMBOL(tdm_adap_disable);
+
+struct tdm_adapter *tdm_get_adapter(int id)
+{
+ struct tdm_adapter *adapter;
+
+ mutex_lock(&tdm_core_lock);
+ adapter = idr_find(&tdm_adapter_idr, id);
+ if (adapter && !try_module_get(adapter->owner))
+ adapter = NULL;
+
+ mutex_unlock(&tdm_core_lock);
+
+ return adapter;
+}
+EXPORT_SYMBOL(tdm_get_adapter);
+
+void tdm_put_adapter(struct tdm_adapter *adap)
+{
+ module_put(adap->owner);
+}
+EXPORT_SYMBOL(tdm_put_adapter);
+
+
+/* Port Level APIs of TDM Framework */
+unsigned int tdm_port_open(struct tdm_driver *driver, void **h_port)
+{
+ struct tdm_port *port;
+ struct tdm_adapter *adap;
+ unsigned long flags;
+ int res = TDM_E_OK;
+
+ if (driver == NULL) {
+ pr_err("driver NULL\n");
+ return -ENODEV;
+ }
+ if (driver->adapter == NULL) {
+ pr_err("adapter NULL\n");
+ return -ENODEV;
+ }
+
+ adap = tdm_get_adapter(driver->adapter->id);
+ if (!adap)
+ return -ENODEV;
+
+ /* This creates an anonymous tdm_port, which may later be
+ * pointed to some slot.
+ *
+ */
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port) {
+ res = -ENOMEM;
+ goto out;
+ }
+
+ port->rx_max_frames = NUM_SAMPLES_PER_FRAME;
+ port->port_cfg.port_mode = e_TDM_PORT_CHANNELIZED;
+
+ port->in_use = 1;
+
+ snprintf(driver->name, TDM_NAME_SIZE, "tdm-dev");
+ port->driver = driver;
+ port->adapter = adap;
+
+ spin_lock_irqsave(&adap->portlist_lock, flags);
+ list_add_tail(&port->list, &adap->myports);
+ spin_unlock_irqrestore(&adap->portlist_lock, flags);
+
+ INIT_LIST_HEAD(&port->mychannels);
+
+ *h_port = port;
+
+out:
+ return res;
+}
+EXPORT_SYMBOL(tdm_port_open);
+
+unsigned int tdm_port_close(void *h_port)
+{
+ struct tdm_adapter *adap;
+ struct tdm_driver *driver;
+ struct tdm_port *port;
+ struct tdm_channel *temp, *channel;
+ unsigned long flags;
+ int res = TDM_E_OK;
+ port = (struct tdm_port *)h_port;
+
+ if (port == NULL) { /* invalid handle*/
+ pr_err("Invalid Handle");
+ return -ENXIO;
+ }
+
+ driver = port->driver;
+
+ if (driver == NULL) {
+ pr_err("driver NULL\n");
+ res = -ENODEV;
+ goto out;
+ }
+ if (driver->adapter == NULL) {
+ pr_err("adapter NULL\n");
+ res = -ENODEV;
+ goto out;
+ }
+
+ list_for_each_entry_safe(channel, temp, &port->mychannels, list) {
+ if (channel)
+ if (channel->in_use) {
+ pr_err("%s: Cannot close port. Channel in use\n",
+ __func__);
+ res = -ENXIO;
+ goto out;
+ }
+ }
+ adap = driver->adapter;
+
+ spin_lock_irqsave(&adap->portlist_lock, flags);
+ list_del(&port->list);
+ spin_unlock_irqrestore(&adap->portlist_lock, flags);
+
+ if (port->p_port_data != NULL) {
+ int i;
+ struct tdm_bd *ch_bd;
+
+ /* If the tdm is in channelised mode,
+ de-allocate the channelised buffer */
+ ch_bd = &(port->p_port_data->rx_data_fifo[0]);
+ for (i = 0; ch_bd && i < TDM_CH_RX_BD_RING_SIZE; i++) {
+ ch_bd->flag = 0;
+ ch_bd++;
+ }
+ ch_bd = &(port->p_port_data->tx_data_fifo[0]);
+ for (i = 0; ch_bd && i < TDM_CH_TX_BD_RING_SIZE; i++) {
+ ch_bd->flag = 0;
+ ch_bd++;
+ }
+ kfree(port->p_port_data);
+ }
+ kfree(port);
+ return res;
+out:
+ if (port)
+ kfree(port->p_port_data);
+ kfree(port);
+ return res;
+}
+EXPORT_SYMBOL(tdm_port_close);
+
+unsigned int tdm_channel_read(void *h_port, void *h_channel,
+ void *p_data, u16 *size)
+{
+ struct tdm_port *port;
+ struct tdm_channel *channel;
+ struct tdm_bd *rx_bd;
+ unsigned long flags;
+ int i, res = TDM_E_OK;
+ unsigned short *buf, *buf1;
+ port = (struct tdm_port *)h_port;
+ channel = (struct tdm_channel *)h_channel;
+
+ if ((port && channel) == 0) { /* invalid handle*/
+ pr_err("%s:Invalid Handle\n", __func__);
+ return -ENXIO;
+ }
+
+ if (!port->in_use)
+ return -EIO;
+ if (!channel->p_ch_data || !channel->in_use)
+ return -EIO;
+
+ spin_lock_irqsave(&channel->p_ch_data->rx_channel_lock, flags);
+ rx_bd = channel->p_ch_data->rx_out_data;
+
+ if (rx_bd->flag) {
+ *size = rx_bd->length;
+ buf = (u16 *) p_data;
+ buf1 = (u16 *)rx_bd->p_data;
+ for (i = 0; i < NUM_SAMPLES_PER_FRAME; i++)
+ buf[i] = buf1[i];
+ rx_bd->flag = 0;
+ rx_bd->offset = 0;
+ channel->p_ch_data->rx_out_data = (rx_bd->wrap) ?
+ channel->p_ch_data->rx_data_fifo : rx_bd + 1;
+
+ } else {
+ spin_unlock_irqrestore(&channel->p_ch_data->rx_channel_lock,
+ flags);
+ pr_info("No Data Available");
+ return -EAGAIN;
+ }
+ spin_unlock_irqrestore(&channel->p_ch_data->rx_channel_lock, flags);
+
+ return res;
+}
+EXPORT_SYMBOL(tdm_channel_read);
+
+
+unsigned int tdm_channel_write(void *h_port, void *h_channel,
+ void *p_data, u16 size)
+{
+ struct tdm_port *port;
+ struct tdm_channel *channel;
+ struct tdm_bd *tx_bd;
+ unsigned long flags;
+ int err = TDM_E_OK;
+ port = (struct tdm_port *)h_port;
+ channel = (struct tdm_channel *)h_channel;
+#ifdef TDM_CORE_DEBUG
+ bool data_flag = 0;
+#endif
+
+ if ((port && channel) == 0) { /* invalid handle*/
+ pr_err("Invalid Handle");
+ return -ENXIO;
+ }
+
+ if (p_data == NULL) { /* invalid data*/
+ pr_err("Invalid Data");
+ return -EFAULT;
+ }
+
+ if (!port->in_use)
+ return -EIO;
+ if (!channel->p_ch_data || !channel->in_use)
+ return -EIO;
+
+ spin_lock_irqsave(&channel->p_ch_data->tx_channel_lock, flags);
+ tx_bd = channel->p_ch_data->tx_in_data;
+
+ if (!tx_bd->flag) {
+ tx_bd->length = size;
+ memcpy(tx_bd->p_data, p_data,
+ size * port->adapter->adapt_cfg.slot_width);
+ tx_bd->flag = 1;
+ tx_bd->offset = 0;
+ channel->p_ch_data->tx_in_data = (tx_bd->wrap) ?
+ channel->p_ch_data->tx_data_fifo : tx_bd+1;
+ port->port_stat.tx_pkt_count++;
+#ifdef TDM_CORE_DEBUG
+ data_flag = 1;
+#endif
+ } else {
+ spin_unlock_irqrestore(&channel->p_ch_data->tx_channel_lock,
+ flags);
+ port->port_stat.tx_pkt_drop_count++;
+ pr_err("E_NO_MEMORY -Failed Transmit");
+ return -ENOMEM;
+ }
+ spin_unlock_irqrestore(&channel->p_ch_data->tx_channel_lock, flags);
+
+#ifdef TDM_CORE_DEBUG
+ if (data_flag) {
+ int k;
+ pr_info("\nTX port:%d - Write - Port TX-%d\n",
+ port->port_id, size);
+ for (k = 0; k < size; k++)
+ pr_info("%x", p_data[k]);
+ pr_info("\n");
+ }
+#endif
+ return err;
+}
+EXPORT_SYMBOL(tdm_channel_write);
+
+/* Driver Function for select and poll. Based on Channel, it sleeps on
+ * waitqueue */
+unsigned int tdm_ch_poll(void *h_channel, unsigned int wait_time)
+{
+ struct tdm_channel *channel;
+ unsigned long timeout = msecs_to_jiffies(wait_time);
+ channel = h_channel;
+
+ if (!channel->p_ch_data || !channel->in_use)
+ return -EIO;
+
+ if (channel->p_ch_data->rx_out_data->flag) {
+ pr_debug("Data Available");
+ return TDM_E_OK;
+ }
+ if (timeout) {
+ wait_event_interruptible_timeout(channel->ch_wait_queue,
+ channel->p_ch_data->rx_out_data->flag,
+ timeout);
+
+ if (channel->p_ch_data->rx_out_data->flag) {
+ pr_debug("Data Available");
+ return TDM_E_OK;
+ }
+ }
+ return -EAGAIN;
+}
+EXPORT_SYMBOL(tdm_ch_poll);
+
+unsigned int tdm_port_get_stats(void *h_port, struct tdm_port_stats *portStat)
+{
+ struct tdm_port *port;
+ int port_num;
+ port = (struct tdm_port *)h_port;
+
+ if (port == NULL || portStat == NULL) { /* invalid handle*/
+ pr_err("Invalid Handle");
+ return -ENXIO;
+ }
+ port_num = port->port_id;
+
+ memcpy(portStat, &port->port_stat, sizeof(struct tdm_port_stats));
+
+ pr_info("TDM Port %d Get Stats", port_num);
+
+ return TDM_E_OK;
+}
+EXPORT_SYMBOL(tdm_port_get_stats);
+
+/* Data handling functions */
+
+static int tdm_data_rx_deinterleave(struct tdm_adapter *adap)
+{
+ struct tdm_port *port, *next;
+ struct tdm_channel *channel, *temp;
+ struct tdm_bd *ch_bd;
+
+ int i, buf_size, ch_data_len;
+ u16 *input_tdm_buffer;
+ u16 *pcm_buffer;
+ int slot_width;
+ int frame_ch_data_size;
+ bool ch_data;
+ int bytes_in_fifo_per_frame;
+ int bytes_slot_offset;
+
+ ch_data_len = NUM_SAMPLES_PER_FRAME;
+ frame_ch_data_size = NUM_SAMPLES_PER_FRAME;
+ ch_data = 0;
+
+ if (!adap) { /* invalid handle*/
+ pr_err("%s: Invalid Handle\n", __func__);
+ return -ENXIO;
+ }
+
+ slot_width = adap->adapt_cfg.slot_width;
+ buf_size = tdm_adap_recv(adap, (void **)&input_tdm_buffer);
+ if (buf_size <= 0 || !input_tdm_buffer)
+ return -EINVAL;
+
+ bytes_in_fifo_per_frame = buf_size/frame_ch_data_size;
+ bytes_slot_offset = bytes_in_fifo_per_frame/slot_width;
+
+ /* de-interleaving for all ports*/
+ list_for_each_entry_safe(port, next, &adap->myports, list) {
+
+ /* if the port is not open */
+ if (!port->in_use)
+ continue;
+
+ list_for_each_entry_safe(channel, temp, &port->mychannels,
+ list) {
+ /* if the channel is not open */
+ if (!channel->in_use || !channel->p_ch_data)
+ continue;
+ ch_bd = channel->p_ch_data->rx_in_data;
+ spin_lock(&channel->p_ch_data->rx_channel_lock);
+ /*if old data is to be discarded */
+ if (use_latest_tdm_data)
+ if (ch_bd->flag) {
+ ch_bd->flag = 0;
+ ch_bd->offset = 0;
+ if (ch_bd == channel->p_ch_data->rx_out_data)
+ channel->p_ch_data->rx_out_data =
+ ch_bd->wrap ?
+ channel->p_ch_data->rx_data_fifo
+ : ch_bd+1;
+ port->port_stat.rx_pkt_drop_count++;
+ }
+ /* if the bd is empty */
+ if (!ch_bd->flag) {
+ if (ch_bd->offset == 0)
+ ch_bd->length = port->rx_max_frames;
+
+ pcm_buffer = ch_bd->p_data + ch_bd->offset;
+ /* De-interleaving the data */
+ for (i = 0; i < ch_data_len; i++) {
+ pcm_buffer[i]
+ = input_tdm_buffer[i*bytes_slot_offset +
+ channel->ch_id];
+ }
+ ch_bd->offset += ch_data_len * slot_width;
+
+ if (ch_bd->offset >=
+ (ch_bd->length - frame_ch_data_size)*
+ (adap->adapt_cfg.slot_width)) {
+ ch_bd->flag = 1;
+ ch_bd->offset = 0;
+ channel->p_ch_data->rx_in_data =
+ ch_bd->wrap ?
+ channel->p_ch_data->rx_data_fifo
+ : ch_bd+1;
+ ch_data = 1;
+ wake_up_interruptible
+ (&channel->ch_wait_queue);
+ }
+ } else {
+ port->port_stat.rx_pkt_drop_count++;
+ }
+ spin_unlock(&channel->p_ch_data->rx_channel_lock);
+ }
+
+ if (ch_data) {
+ /* Wake up the Port Data Poll event */
+#ifdef TDM_CORE_DEBUG
+ pr_info("Port RX-%d-%d\n", channel->ch_id, ch_data_len);
+ for (i = 0; i < ch_data_len; i++)
+ pr_info("%x", pcm_buffer[i]);
+ pr_info("\n");
+#endif
+ port->port_stat.rx_pkt_count++;
+ ch_data = 0;
+ }
+ }
+ return TDM_E_OK;
+}
+
+static int tdm_data_tx_interleave(struct tdm_adapter *adap)
+{
+ struct tdm_port *port, *next;
+ struct tdm_channel *channel, *temp;
+ struct tdm_bd *ch_bd;
+ int i, buf_size, ch_data_len = NUM_SAMPLES_PER_FRAME;
+ bool last_data = 0;
+ u16 *output_tdm_buffer;
+ u16 *pcm_buffer;
+ int frame_ch_data_size = NUM_SAMPLES_PER_FRAME;
+ int bytes_in_fifo_per_frame;
+ int bytes_slot_offset;
+
+#ifdef TDM_CORE_DEBUG
+ u8 data_flag = 0;
+#endif
+
+ if (adap == NULL) { /* invalid handle*/
+ pr_err("%s: Invalid Handle\n", __func__);
+ return -ENXIO;
+ }
+
+ buf_size = tdm_adap_get_write_buf(adap, (void **)&output_tdm_buffer);
+ if (buf_size <= 0 || !output_tdm_buffer)
+ return -EINVAL;
+
+ bytes_in_fifo_per_frame = buf_size/frame_ch_data_size;
+ bytes_slot_offset = bytes_in_fifo_per_frame/adap->adapt_cfg.slot_width;
+
+
+ memset(output_tdm_buffer, 0, sizeof(buf_size));
+
+ list_for_each_entry_safe(port, next, &adap->myports, list) {
+
+ /* check if the port is open */
+ if (!port->in_use)
+ continue;
+
+ list_for_each_entry_safe(channel, temp, &port->mychannels,
+ list) {
+ pr_debug("TX-Tdm %d (slots-)", channel->ch_id);
+
+
+ /* if the channel is open */
+ if (!channel->in_use || !channel->p_ch_data)
+ continue;
+
+ spin_lock(&channel->p_ch_data->tx_channel_lock);
+ if (!channel->in_use || !channel->p_ch_data)
+ continue;
+ ch_bd = channel->p_ch_data->tx_out_data;
+ if (ch_bd->flag) {
+ pcm_buffer = (u16 *)((uint8_t *)ch_bd->p_data +
+ ch_bd->offset);
+ /*if the buffer has less frames than required */
+ if (frame_ch_data_size >=
+ ((ch_bd->length) - (ch_bd->offset/
+ adap->adapt_cfg.slot_width))) {
+ ch_data_len =
+ (ch_bd->length) - (ch_bd->offset/
+ adap->adapt_cfg.slot_width);
+ last_data = 1;
+ } else {
+ ch_data_len = frame_ch_data_size;
+ }
+ /* Interleaving the data */
+ for (i = 0; i < ch_data_len; i++) {
+ /* TODO- need to be genric for any size
+ assignment*/
+ output_tdm_buffer[channel->ch_id +
+ bytes_slot_offset * i] =
+ pcm_buffer[i];
+ }
+ /* If all the data of this buffer is
+ transmitted */
+ if (last_data) {
+ ch_bd->flag = 0;
+ ch_bd->offset = 0;
+ channel->p_ch_data->tx_out_data =
+ ch_bd->wrap ?
+ channel->p_ch_data->tx_data_fifo
+ : ch_bd+1;
+ port->port_stat.tx_pkt_conf_count++;
+ } else {
+ ch_bd->offset += ch_data_len *
+ (adap->adapt_cfg.slot_width);
+ }
+#ifdef TDM_CORE_DEBUG
+ data_flag = 1;
+#endif
+ }
+ spin_unlock(&channel->p_ch_data->tx_channel_lock);
+ }
+ }
+
+#ifdef TDM_CORE_DEBUG
+ if (data_flag) {
+ pr_info("TX-TDM Interleaved Data-\n");
+ for (i = 0; i < 64; i++)
+ pr_info("%x", output_tdm_buffer[i]);
+ pr_info("\n");
+ }
+#endif
+ return TDM_E_OK;
+}
+
+/* Channel Level APIs of TDM Framework */
+int tdm_channel_open(u16 chanid, u16 ch_width, struct tdm_port *port,
+ void **h_channel)
+{
+ struct tdm_channel *channel, *temp;
+ unsigned long flags;
+ struct tdm_ch_data *p_ch_data;
+ int res = TDM_E_OK;
+
+ if (!(port && h_channel)) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ if (ch_width != 1) {
+ pr_err("%s: Mode not supported\n", __func__);
+ return -EINVAL;
+ }
+
+ list_for_each_entry_safe(channel, temp, &port->mychannels, list) {
+ if (channel->ch_id == chanid) {
+ pr_err("%s: Channel %d already open\n",
+ __func__, chanid);
+ return -EINVAL;
+ }
+ }
+
+ channel = kzalloc(sizeof(*channel), GFP_KERNEL);
+ if (!channel) {
+ res = -ENOMEM;
+ goto out;
+ }
+
+ init_waitqueue_head(&channel->ch_wait_queue);
+ p_ch_data = kzalloc(sizeof(struct tdm_ch_data), GFP_KERNEL);
+ if (!p_ch_data) {
+ res = -ENOMEM;
+ goto outdata;
+ }
+
+ p_ch_data->rx_data_fifo[TDM_CH_RX_BD_RING_SIZE-1].wrap = 1;
+ p_ch_data->tx_data_fifo[TDM_CH_TX_BD_RING_SIZE-1].wrap = 1;
+
+ p_ch_data->rx_in_data = p_ch_data->rx_data_fifo;
+ p_ch_data->rx_out_data = p_ch_data->rx_data_fifo;
+ p_ch_data->tx_in_data = p_ch_data->tx_data_fifo;
+ p_ch_data->tx_out_data = p_ch_data->tx_data_fifo;
+ spin_lock_init(&p_ch_data->rx_channel_lock);
+ spin_lock_init(&p_ch_data->tx_channel_lock);
+
+ channel->p_ch_data = p_ch_data;
+
+ channel->ch_id = chanid;
+ channel->ch_cfg.first_slot = chanid;
+ channel->ch_cfg.num_slots = 1; /* This is 1 for channelized mode and
+ configurable for other modes */
+ channel->port = port;
+ channel->in_use = 1;
+
+ spin_lock_irqsave(&port->ch_list_lock, flags);
+ list_add_tail(&channel->list, &port->mychannels);
+ spin_unlock_irqrestore(&port->ch_list_lock, flags);
+
+ *h_channel = channel;
+
+ return res;
+
+outdata:
+ kfree(channel);
+out:
+ return res;
+}
+EXPORT_SYMBOL(tdm_channel_open);
+
+int tdm_channel_close(u16 chanid, u16 ch_width, struct tdm_port *port,
+ struct tdm_channel *h_channel)
+{
+ struct tdm_channel *channel;
+ unsigned long flags;
+ int res = TDM_E_OK;
+ channel = h_channel;
+
+ if (!(port && channel)) {
+ pr_err("%s: Invalid handle\n", __func__);
+ res = -EINVAL;
+ goto out;
+ }
+
+ if (ch_width != 1) {
+ pr_err("%s: Mode not supported\n", __func__);
+ res = -EINVAL;
+ goto out;
+ }
+
+ spin_lock_irqsave(&port->ch_list_lock, flags);
+ list_del(&channel->list);
+ spin_unlock_irqrestore(&port->ch_list_lock, flags);
+
+out:
+ if (channel)
+ kfree(channel->p_ch_data);
+ kfree(channel);
+ return res;
+}
+EXPORT_SYMBOL(tdm_channel_close);
+
+void init_config_adapter(struct tdm_adapter *adap)
+{
+ struct fsl_tdm_adapt_cfg default_adapt_cfg = {
+ .loopback = e_TDM_PROCESS_NORMAL,
+ .num_ch = NUM_CHANNELS,
+ .ch_size_type = CHANNEL_16BIT_LIN,
+ .frame_len = NUM_SAMPLES_PER_FRAME,
+ .num_frames = NUM_SAMPLES_PER_FRAME,
+ .adap_mode = e_TDM_ADAPTER_MODE_NONE
+ };
+
+ default_adapt_cfg.slot_width = default_adapt_cfg.ch_size_type/3 + 1;
+
+ memcpy(&adap->adapt_cfg, &default_adapt_cfg,
+ sizeof(struct fsl_tdm_adapt_cfg));
+
+ return;
+}
+EXPORT_SYMBOL(init_config_adapter);
+
+static void tdm_data_tasklet_fn(unsigned long data)
+{
+ struct tdm_adapter *adapter;
+ adapter = (struct tdm_adapter *)data;
+ if (adapter != NULL) {
+ tdm_data_tx_interleave(adapter);
+ tdm_data_rx_deinterleave(adapter);
+ }
+}
+
+/*
+ * add_tdm_phy - declare tdm phy, use dynamic device number
+ * @tdm_phy: tdm_phy to be added
+ * Context: can sleep
+ * This routine is used to declare a TDM phy device and add it to the list of
+ * avalable tdm phy devices
+ */
+int add_tdm_phy(struct tdm_phy *tdm_phy)
+{
+ int res = TDM_E_OK;
+ int index;
+ struct tdm_adapter *adap, *next;
+ list_add_tail(&tdm_phy->list, &tdm_phy_list);
+ mutex_lock(&tdm_core_lock);
+ list_for_each_entry_safe(adap, next, &tdm_adapter_list, list) {
+ /* Check if any adapter has our phandle */
+ for (index = 0; index < adap->num_tdm_phy; index++)
+ if (*(int *)tdm_phy->phandle_prop ==
+ *((int *)adap->phandle_prop) + index) {
+ mutex_unlock(&tdm_core_lock);
+ return attach_phy_adapter(adap, tdm_phy, index);
+ }
+ }
+ mutex_unlock(&tdm_core_lock);
+ return res;
+}
+EXPORT_SYMBOL(add_tdm_phy);
+
+int attach_phy_adapter(struct tdm_adapter *adapter,
+ struct tdm_phy *tdm_phy, int phy_id)
+{
+ struct fsl_tdm_adapt_cfg *tdm_config;
+ tdm_phy->tdm_adapter = adapter;
+ adapter->tdm_phy[phy_id] = tdm_phy;
+ /* Get TDM configuration from adapter */
+ tdm_config = &adapter->adapt_cfg;
+ /*
+ * Call config function of tdm phy with adapter configuration so that
+ * it can configure itself based on the configuration passed by adapter
+ */
+ tdm_phy->configure_phy(tdm_config);
+ return 1;
+}
+
+int get_tdm_phy(struct tdm_adapter *adapter, void *req_phy_handle)
+{
+ int index;
+ struct tdm_phy *tdm_phy, *next;
+ list_for_each_entry_safe(tdm_phy, next, &tdm_phy_list, list) {
+ for (index = 0; index < adapter->num_tdm_phy; index++)
+ if (*(int *)tdm_phy->phandle_prop ==
+ *((int *)req_phy_handle + index))
+ return attach_phy_adapter(adapter,
+ tdm_phy, index);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(get_tdm_phy);
+
+MODULE_AUTHOR("Hemant Agrawal <hemant@freescale.com> and "
+ "Rajesh Gumasta <rajesh.gumasta@freescale.com>");
+MODULE_DESCRIPTION("TDM Driver Framework Core");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index fed3def..b8214f3 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -444,6 +444,17 @@ struct i2c_device_id {
__attribute__((aligned(sizeof(kernel_ulong_t))));
};
+/* tdm */
+
+#define TDM_NAME_SIZE 20
+#define TDM_MODULE_PREFIX "tdm:"
+
+struct tdm_device_id {
+ char name[TDM_NAME_SIZE];
+ kernel_ulong_t driver_data /* Data private to the driver */
+ __attribute__((aligned(sizeof(kernel_ulong_t))));
+};
+
/* spi */
#define SPI_NAME_SIZE 32
diff --git a/include/linux/tdm.h b/include/linux/tdm.h
new file mode 100644
index 0000000..555d22e
--- /dev/null
+++ b/include/linux/tdm.h
@@ -0,0 +1,379 @@
+/* include/linux/tdm.h
+ *
+ * Copyright (C) 2012 Freescale Semiconductor, Inc, All rights reserved.
+ *
+ * tdm.h - definitions for the tdm-device framework interface
+ *
+ * Author:Hemant Agrawal <hemant@freescale.com>
+ * Rajesh Gumasta <rajesh.gumasta@freescale.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+#ifndef _LINUX_TDM_H
+#define _LINUX_TDM_H
+
+#ifdef __KERNEL__
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/device.h> /* for struct device */
+#include <linux/sched.h> /* for completion */
+#include <linux/mutex.h>
+#include <linux/interrupt.h>
+
+#define CHANNEL_8BIT_LIN 0 /* 8 bit linear */
+#define CHANNEL_8BIT_ULAW 1 /* 8 bit Mu-law */
+#define CHANNEL_8BIT_ALAW 2 /* 8 bit A-law */
+#define CHANNEL_16BIT_LIN 3 /* 16 bit Linear */
+
+#define NUM_CHANNELS 16
+#define NUM_SAMPLES_PER_MS 8 /* 8 samples per milli sec per
+ channel. Req for voice data */
+#define NUM_MS 10
+#define NUM_SAMPLES_PER_FRAME (NUM_MS * NUM_SAMPLES_PER_MS) /* Number of
+ samples for 1 client buffer */
+#define NUM_OF_TDM_BUF 3
+#define TDM_PHY_NAME_LEN 20
+#define MAX_TDM_PHY 10
+
+/* General options */
+
+struct tdm_adapt_algorithm;
+struct tdm_adapter;
+struct tdm_port;
+struct tdm_driver;
+
+/* Align addr on a size boundary - adjust address up if needed */
+/* returns min value greater than size which is multiple of alignment */
+static inline int ALIGN_SIZE(u64 size, u32 alignment)
+{
+ return (size + alignment - 1) & (~(alignment - 1));
+}
+
+/**
+ * struct tdm_driver - represent an TDM device driver
+ * @class: What kind of tdm device we instantiate (for detect)
+ * @id:Driver id
+ * @name: Name of the driver
+ * @attach_adapter: Callback for device addition (for legacy drivers)
+ * @detach_adapter: Callback for device removal (for legacy drivers)
+ * @probe: Callback for device binding
+ * @remove: Callback for device unbinding
+ * @shutdown: Callback for device shutdown
+ * @suspend: Callback for device suspend
+ * @resume: Callback for device resume
+ * @command: Callback for sending commands to device
+ * @id_table: List of TDM devices supported by this driver
+ * @list: List of drivers created (for tdm-core use only)
+ */
+struct tdm_driver {
+ unsigned int class;
+ unsigned int id;
+ char name[TDM_NAME_SIZE];
+
+ int (*attach_adapter)(struct tdm_adapter *);
+ int (*detach_adapter)(struct tdm_adapter *);
+
+ /* Standard driver model interfaces */
+ int (*probe)(const struct tdm_device_id *);
+ int (*remove)(void);
+
+ /* driver model interfaces that don't relate to enumeration */
+ void (*shutdown)(void);
+ int (*suspend)(pm_message_t mesg);
+ int (*resume)(void);
+
+ /* a ioctl like command that can be used to perform specific functions
+ * with the device.
+ */
+ int (*command)(unsigned int cmd, void *arg);
+
+ const struct tdm_device_id *id_table;
+
+ /* The associated adapter for this driver */
+ struct tdm_adapter *adapter;
+ struct list_head list;
+};
+
+/* tdm per port statistics structure, used for providing and storing tdm port
+ * statistics.
+ */
+struct tdm_port_stats {
+ unsigned int rx_pkt_count; /* Rx frame count per channel */
+ unsigned int rx_pkt_drop_count; /* Rx drop count per channel to
+ clean space for new buffer */
+ unsigned int tx_pkt_count; /* Tx frame count per channel */
+ unsigned int tx_pkt_conf_count; /* Tx frame confirmation count per
+ channel */
+ unsigned int tx_pkt_drop_count; /* Tx drop count per channel due to
+ queue full */
+};
+
+
+/* tdm Buffer Descriptor, used for Creating Interleaved and De-interleaved
+ * FIFOs
+ */
+struct tdm_bd {
+ unsigned char flag; /* BD is full or empty */
+ unsigned char wrap; /* BD is last in the queue */
+ unsigned short length; /* Length of Data in BD */
+ /*TODO: use dyanmic memory */
+ unsigned short p_data[NUM_SAMPLES_PER_FRAME]; /* Data Pointer */
+ unsigned long offset; /* Offset of the Data Pointer to be used */
+};
+
+#define TDM_CH_RX_BD_RING_SIZE 3
+#define TDM_CH_TX_BD_RING_SIZE 3
+
+/* tdm RX-TX Channelised Data */
+struct tdm_port_data {
+ struct tdm_bd rx_data_fifo[TDM_CH_RX_BD_RING_SIZE]; /* Rx Channel Data
+ BD Ring */
+ struct tdm_bd *rx_in_data; /* Current Channel Rx BD to be filled by
+ de-interleave function */
+ struct tdm_bd *rx_out_data; /* Current Channel Rx BD to be
+ read by App */
+ struct tdm_bd tx_data_fifo[TDM_CH_TX_BD_RING_SIZE]; /* Tx Channel Data
+ BD Ring */
+ struct tdm_bd *tx_in_data; /* Current Channel Tx BD to be
+ filled by App */
+ struct tdm_bd *tx_out_data; /* Current Channel Tx BD to be read by
+ interleave function */
+ spinlock_t rx_channel_lock; /* Spin Lock for Rx Channel */
+ spinlock_t tx_channel_lock; /* Spin Lock for Tx Channel */
+};
+
+/* structure tdm_port_cfg - contains configuration params for a port */
+struct tdm_port_cfg {
+ unsigned short port_mode;
+};
+
+/* struct tdm_port - represent an TDM ports for a device */
+struct tdm_port {
+ unsigned short port_id;
+ unsigned short in_use; /* Port is enabled? */
+ uint16_t rx_max_frames; /* Received Port frames
+ before allowing Read Operation in
+ Port Mode */
+
+ struct tdm_port_stats port_stat;/* A structure parameters defining
+ TDM port statistics. */
+ struct tdm_port_data *p_port_data; /* a structure parameters
+ defining tdm channelised data */
+
+ struct tdm_driver *driver; /* driver for this port */
+ struct tdm_adapter *adapter; /* adapter for this port */
+ struct list_head list; /* list of ports */
+ struct list_head mychannels; /* list of channels, created on this
+ port*/
+ spinlock_t ch_list_lock; /* Spin Lock for channel_list */
+ struct tdm_port_cfg port_cfg;/* A structure parameters defining
+ TDM port configuration. */
+};
+
+/* tdm RX-TX Channelised Data */
+struct tdm_ch_data {
+ struct tdm_bd rx_data_fifo[TDM_CH_RX_BD_RING_SIZE]; /* Rx Port Data BD
+ Ring */
+ struct tdm_bd *rx_in_data; /* Current Port Rx BD to be filled by
+ de-interleave function */
+ struct tdm_bd *rx_out_data; /* Current Port Rx BD to be read by App */
+ struct tdm_bd tx_data_fifo[TDM_CH_TX_BD_RING_SIZE]; /* Tx Port Data BD
+ Ring */
+ struct tdm_bd *tx_in_data; /* Current Port Tx BD to be filled by
+ App */
+ struct tdm_bd *tx_out_data; /* Current Port Tx BD to be read by
+ interleave function */
+ spinlock_t rx_channel_lock; /* Spin Lock for Rx Port */
+ spinlock_t tx_channel_lock; /* Spin Lock for Tx Port */
+};
+
+/* Channel config params */
+struct tdm_ch_cfg {
+ unsigned short num_slots;
+ unsigned short first_slot;
+};
+
+/* struct tdm_channel- represent a TDM channel for a port */
+struct tdm_channel {
+ u16 ch_id; /* logical channel number */
+ struct list_head list; /* list of channels in a port*/
+ struct tdm_port *port; /* port for this channel */
+ u16 in_use; /* channel is enabled? */
+ struct tdm_ch_cfg ch_cfg; /* channel configuration */
+ struct tdm_ch_data *p_ch_data; /* data storage space for channel */
+ wait_queue_head_t ch_wait_queue;/* waitQueue for RX Channel Data */
+};
+
+/* tdm_adapt_algorithm is for accessing the routines of device */
+struct tdm_adapt_algorithm {
+ u32 (*tdm_read)(struct tdm_adapter *, u16 **);
+ u32 (*tdm_get_write_buf)(struct tdm_adapter *, u16 **);
+ u32 (*tdm_write)(struct tdm_adapter *, void * , unsigned int len);
+ int (*tdm_enable)(struct tdm_adapter *);
+ int (*tdm_disable)(struct tdm_adapter *);
+};
+
+/* tdm_adapter_mode is to define in mode of the device */
+enum tdm_adapter_mode {
+ e_TDM_ADAPTER_MODE_NONE = 0x00,
+ e_TDM_ADAPTER_MODE_T1 = 0x01,
+ e_TDM_ADAPTER_MODE_E1 = 0x02,
+ e_TDM_ADAPTER_MODE_T1_RAW = 0x10,
+ e_TDM_ADAPTER_MODE_E1_RAW = 0x20,
+};
+
+/* tdm_port_mode defines the mode in which the port is configured to operate
+ * It can be channelized/full/fractional.
+ */
+enum tdm_port_mode {
+ e_TDM_PORT_CHANNELIZED = 0 /* Channelized mode */
+ , e_TDM_PORT_FULL = 1 /* Full mode */
+ , e_TDM_PORT_FRACTIONAL = 2 /* Fractional mode */
+};
+
+/* tdm_process_mode used for testing the tdm device in normal mode or internal
+ * loopback or external loopback
+ */
+enum tdm_process_mode {
+ e_TDM_PROCESS_NORMAL = 0 /* Normal mode */
+ , e_TDM_PROCESS_INT_LPB = 1 /* Internal loop mode */
+ , e_TDM_PROCESS_EXT_LPB = 2 /* External Loopback mode */
+};
+
+
+/* TDM configuration parameters */
+struct fsl_tdm_adapt_cfg {
+ u8 num_ch; /* Number of channels in this adpater */
+ u8 ch_size_type; /* reciever/transmit channel
+ size for all channels */
+ u8 slot_width; /* 1 or 2 Is defined by channel type */
+ u8 frame_len; /* Length of frame in samples */
+ u32 num_frames;
+ u8 loopback; /* loopback or normal */
+ u8 adap_mode; /* 0=None, 1= T1, 2= T1-FULL, 3=E1,
+ 4 = E1-FULL */
+ int max_num_ports; /* Not Used: Max Number of ports that
+ can be created on this adapter */
+ int max_timeslots; /* Max Number of timeslots that are
+ supported on this adapter */
+ int tdm_tx_clk; /* Value of tdm transmit clk */
+ int tdm_rx_clk; /* Value of tdm receive clk */
+};
+
+/*
+ * tdm_adapter is the structure used to identify a physical tdm device along
+ * with the access algorithms necessary to access it.
+ */
+struct tdm_adapter {
+ struct module *owner; /* owner of the adapter module */
+ unsigned int id; /* Adapter Id */
+ unsigned int class; /* classes to allow probing for */
+ unsigned int drv_count; /* Number of drivers associated with the
+ adapter */
+
+ const struct tdm_adapt_algorithm *algo; /* the algorithm to access the
+ adapter*/
+
+ char name[TDM_NAME_SIZE]; /* Name of Adapter */
+ struct mutex adap_lock;
+ struct device *parent; /*Not Used*/
+
+ struct tasklet_struct tdm_data_tasklet; /* tasklet handle to perform
+ data processing*/
+ int tasklet_conf; /* flag for tasklet configuration */
+ int tdm_rx_flag;
+
+ struct list_head myports; /* list of ports, created on this
+ adapter */
+ struct list_head list;
+ spinlock_t portlist_lock; /* Spin Lock for port_list */
+ void *data;
+ struct fsl_tdm_adapt_cfg adapt_cfg;
+ int num_tdm_phy; /* Number of tdm phys available */
+ struct tdm_phy *tdm_phy[MAX_TDM_PHY]; /* Stores tdm phys */
+ void *phandle_prop; /* Pointer to phandle property */
+};
+
+/*
+ * tdm_phy is the structure used to identify a physiscal line control device
+ * and callbacks required to configure it.
+ */
+struct tdm_phy {
+ char name[TDM_PHY_NAME_LEN];
+ struct device_node *np;
+ int (*configure_phy)(struct fsl_tdm_adapt_cfg *);
+ struct tdm_adapter *tdm_adapter; /* Will point to tdm adpater to which
+ this phy is conected */
+ struct list_head list;
+ void *phandle_prop; /* Pointer to phandle property */
+};
+
+struct tdm_phy_priv {
+ struct device *device;
+ unsigned int type; /* Type of tdm_phy: can be SLIC/E1/T1 */
+ struct tdm_phy phy;
+};
+
+
+static inline void *tdm_get_adapdata(const struct tdm_adapter *dev)
+{
+ return dev->data;
+}
+
+static inline void tdm_set_adapdata(struct tdm_adapter *dev, void *data)
+{
+ dev->data = data;
+}
+
+/* functions exported by tdm.o */
+
+extern int tdm_add_adapter(struct tdm_adapter *);
+extern int tdm_del_adapter(struct tdm_adapter *);
+extern int tdm_register_driver(struct tdm_driver *);
+extern void tdm_del_driver(struct tdm_driver *);
+extern void tdm_unregister_driver(struct tdm_driver *);
+extern void init_config_adapter(struct tdm_adapter *);
+
+extern unsigned int tdm_port_open(struct tdm_driver *, void **);
+extern unsigned int tdm_port_close(void *);
+extern unsigned int tdm_port_ioctl(void *, unsigned int, unsigned long);
+extern unsigned int tdm_channel_read(void *, void *, void *, u16 *);
+extern unsigned int tdm_channel_write(void *, void * , void *, u16);
+extern unsigned int tdm_ch_poll(void *, unsigned int);
+
+extern int tdm_channel_open(u16, u16, struct tdm_port *, void **);
+extern int tdm_channel_close(u16, u16, struct tdm_port *,
+ struct tdm_channel *);
+extern int add_tdm_phy(struct tdm_phy *);
+extern int attach_phy_adapter(struct tdm_adapter *,
+ struct tdm_phy *, int phy_id);
+extern int get_tdm_phy(struct tdm_adapter *, void *);
+
+static inline int tdm_add_driver(struct tdm_driver *driver)
+{
+ return tdm_register_driver(driver);
+}
+
+extern struct tdm_adapter *tdm_get_adapter(int id);
+extern void tdm_put_adapter(struct tdm_adapter *adap);
+
+#endif /* __KERNEL__ */
+
+#define TDM_E_OK 0
+
+#endif /* _LINUX_TDM_H */
--
1.7.6.GIT
^ permalink raw reply related
* [PATCH 1/4][v2] Adding Documentation for TDM
From: Sandeep Singh @ 2013-03-05 12:41 UTC (permalink / raw)
To: linuxppc-dev; +Cc: Sandeep Singh, Poonam Aggrwal, linux-arm-kernel
tdm-summary.txt contains general description about TDM.
tdm-framework.txt contains specific description of TDM framework.
Signed-off-by: Sandeep Singh <Sandeep@freescale.com>
Signed-off-by: Poonam Aggrwal <poonam.aggrwal@freescale.com>
---
Documentation/tdm/tdm-framework.txt | 264 +++++++++++++++++++++++++++++++++++
Documentation/tdm/tdm-summary.txt | 103 ++++++++++++++
2 files changed, 367 insertions(+), 0 deletions(-)
create mode 100644 Documentation/tdm/tdm-framework.txt
create mode 100644 Documentation/tdm/tdm-summary.txt
diff --git a/Documentation/tdm/tdm-framework.txt b/Documentation/tdm/tdm-framework.txt
new file mode 100644
index 0000000..e8704a4
--- /dev/null
+++ b/Documentation/tdm/tdm-framework.txt
@@ -0,0 +1,264 @@
+This document gives an overview of TDM framework and its interface with low
+level drivers and upper level users/clients.
+
+Terminology:
+============
+1. Adapter or TDM adapter: Refers to an instance of TDM controller/device on
+ the system.
+2. TDM channel: The channel is the smallest entity on which all the TDM
+ read/write operations will occur. Technically all channels map to a set of
+ consecutive time slots on the physical TDM frame. The channels will be
+ dynamically created and destroyed using tdm_open_channel and
+ tdm_close_channel.
+3. TDM frame: Is a set of TDM channels which is transmitted sequentially over
+ time. The frame start is identified by a frame sync signal that is briefly
+ asserted at the beginning of each frame.
+
+<----------TDM Frame 0------------>|<-----TDM Frame 1----------------->
+
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+| 0 | 1 | 2 | 3 | 4 | ...| n | 0 | 1 | 2 | 3 | 4 | ...| n |
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+<----> <---->
+ ch 0 ch 0
+
+4. TDM client: Application/driver which registers with TDM framework to use TDM
+ device.
+
+TDM modes:
+==========
+A TDM device can operate in one of the following modes:
+1. Single port full mode - Single user, no interleaving
+2. Single port channelised mode (raw, E1, T1)- many users using different channels
+3. Single port fractional mode -
+4. Multi port mode - multiple users using different ports in different configurations.
+
+All the above configurations differ in number of TDM client they support,
+number of TDM channels and number of TDM ports.
+
+Currently we are supporting only single port channelised mode. Hence all the explanations below
+refer to channelised mode of TDM. This framework can be easily extended to support other modes.
+
+Single port Channelised Mode:
+=============================
+In single port channelised mode there can be only one port and each channel can have only one time slot.
+The number of active channels can be less than the maximum supported channels/slots.
+
+<----------TDM Frame 0------------>|<-----TDM Frame 1----------------->
+
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+| 0 | 1 | 2 | 3 | 4 | ...| n | 0 | 1 | 2 | 3 | 4 | ...| n |...
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+<----><---> <---->
+ ch 0 ch1 ch 0
+client0 client1
+
+TDM Subsystem Overview:
+=======================
+
+-------------------------------------------------------------------
+ |------------------------|
+ | kernel mode TDM clients|
+ |------------------------|
+ ||
+ ||
+ client register
+ ||
+ ||
+ \/
+ ______________________________________________________________
+ | |
+ | client interface |
+ |------------------------------------------------------------|
+ | TDM Subsystem Framework |
+ | (tdm-core.c) |
+ | |
+ | ->buffer handling |
+ | ->interleaving/de-interleaving |
+ | |
+ |------------------------------------------------------------|
+ | TDM interface Line control interface |
+ |____________________________________________________________|
+ /\ /\
+ || ||
+ device register device register
+ || ||
+ || ||
+
+ fsl_tdm.c ucc_tdm.c slic_zarlink.c framer.c
+--------------------------------------------------------------------------
+_______________________ _____________________ ________ ________
+| | | | | | | |
+|[h/w] TDM controller | |UCC TDM controller | | SLIC | |Framer|
+|_____________________| |___________________| |______| |______|
+
+
+TDM Adapter Registration:
+=========================
+All the TDM adapter drivers will get registered as platform drivers to Linux.
+For every instance of the TDM adapter the relevant driver will be probed.
+As part of probe the driver will
+1. Do the basic initialization in terms of memory allocation for various
+ driver specific data structures, interrupts registration, etc.
+2. Initialize the TDM adapter and configure it in default configuration.
+ like operating mode, number of channels, channel type, etc.
+3. Add the TDM adapter to the TDM Framework via tdm_add_adapter() API.
+ As a result TDM framework will add this adapter to it's queue of
+ available adapters. As part of this adapter registration TDM framework
+ is also supplied a list of access algorithms for the particular TDM
+ adapter.
+4. Notifies the clients
+
+TDM Client Registration:
+========================
+Every TDM client gets itself registered with the TDM framework layer as
+a TDM driver using the API tdm_add_driver(). As part of this the TDM client
+supplies to the TDM framework the adapter with which it wants to hook and
+the function pointers of attach and detach functions which must be called
+as soon as the requested adapter is available.
+
+As a result the TDM framework keeps association of TDM adapters and TDM
+client drivers.
+As soon as this association gets established a tasklet is created for the
+adapter which is handled by tdm_data_tasklet_fn. The primary function of
+this tasklet acts as an interface to transfer the TDM data between the
+TDM adapter and the TDM client drivers.
+
+Currently TDM adaper can only be used in the default configuration.
+ie the configuration cannot be modified by TDM clients. This will be
+enhanced in future.
+
+Line Control Device Registration:
+=================================
+Each line control device has to add itself to list of available tdm phy device
+list by calling tdm_add_phy. At this point the framework traverses the list of
+available tdm devices to check if this tdm phy belongs to an already
+registered tdm device. If a match is found, then this phy attaches itself to
+that tdm device. Similarly the list of available tdm phys is traversed when a
+new tdm device registers itself.
+
+Data handling:
+==============
+Some basic assumptions about data handling:
+
+1. As per standard voice rate of 8Khz or 8192Hz. Which means 8192 samples must
+be sent every second. So if there are multiple clients sending voice data
+over TDM interface the rate should be such that the individual samples
+sent by them must be transmitted at 8 KHz.
+
+This is defined in the driver as
+
+ #define CH_1_MS_FRAMES 8
+
+2. Number of milliseconds at which TDM Rx interrupts occur
+This is basically the time for which the TDM data is sent in one Tx or Rx
+cycle of TDM controller hardware. In one DMA we send the data for 10ms.
+This gives enough time so that no buffer overflow or under-run occurs for
+transmit and receive respectively.
+
+ #define NUM_MS 10
+
+3. TDM has programmable slot length (8 bits or 16 bits). This can be configured by:
+
+ #define CHANNEL_8BIT_LIN 0 /* 8 bit linear */
+ #define CHANNEL_8BIT_ULAW 1 /* 8 bit Mu-law */
+ #define CHANNEL_8BIT_ALAW 2 /* 8 bit A-law */
+ #define CHANNEL_16BIT_LIN 3 /* 16 bit Linear */
+
+depending on the type of sample. For example the sample could be 16 bit linear
+or 8bit u-law encoded etc. Presently only word length of 16 is supported
+which is the default configuration.
+
+4. Number of channels means the total number of channels per TDM port.
+For example for E1 mode it will be 24, for T1 it will be 32, etc.
+There can also be raw mode, where the use case is not E1 or T1.
+Here the number of channels can be any number as per the use case.
+
+The whole framework follows a triple buffer approach to make sure that TDM data
+is played continuously at the desired rate.
+
+Buffers Involved:
+=================
+
+1.TDM driver or device buffers:
+These buffers are the device level buffers. They contain the TDM data
+which is transmitted/received on the TDM physical signals. As such these buffers must be
+allocated from driver layer so that all the hardware requirements are met.
+As an optimized design to remove extra memcopies, the client can
+pass the data in the same buffers. But this is only true for full mode of
+TDM. Where the user data can be straightaway passed to the hardware for
+transmission. Although in other cases memcopy cannot be avoided, because
+the framework layer will have to interleave the individual channels data to
+create the TDM frame data buffer. The size of this buffer will be governed by
+
+For channelised mode:
+The size of this buffer will be
+
+- number of channels
+- number of slots per channel
+- number of bytes per slot
+- number of frames per ms
+- number of ms
+
+For a channelised mode with single port the size of the device level buffer
+will be:
+channels * slots per channel * bytes per slot * frames per ms * number of ms
+channels * 1 * NUM_BYTES_PER_SLOT * NUM_MS * CH_1_MS_FRAMES
+
+There will be 3 such buffers.
+
+2.Channel level buffers:
+In case the TDM device is configured for multiport/multichannel the Framework
+layer needs to maintain the data for each channel. Hence for each channel
+opened a Buffer Descriptor ring of 3 BDs(see note below) is allocated both for
+transmit and receive. The client reads from/writes to the buffers pointed by
+these BD rings.
+
+The framework layer maintains a Data Process Tasklet per TDM
+device which is scheduled from every Rx interrupt. The interrupt handling
+periodicity is governed by the TDM data buffer size configured in the above
+section.
+The data tasklet when scheduled, will do Rx and Tx processing to copy
+the data from/to the channel specific interleaved buffers. The TDM
+controller will DMA the data which is copied in the interleaved buffers
+ or device level buffers.
+
+TDM framework provides the port level APIs and channel level APIs to the TDM
+client drivers to send and receive the respective data on different TDM slots.
+
+
+num of buffers = 3
+
+TDM client1 TDM Client2
+
+buf0------->buf1 buf0------->buf1
+^ | ^ |
+| V | V
+----buf2------ ------buf2----
+ | |
+ | |
+ | |
+ V V
+-----------------------------------------
+| |
+| DATA Tasklet |
+| |
+-----------------------------------------
+ |
+ |
+ V
+-----------------------------------------
+| TDM buffer interleaved * 3 |
+-----------------------------------------
+
+
+Not Implemented/Future work:
+============================
+1. TDM client will use the default configuration which is done at init time and
+ is not configurable. In future this should be made configurable as per the
+ need of client.
+2. The TDM framework still needs to be enhanced to configure the ports and their
+ attributes. Currently only single port channelised mode is supported.
+3. Line control interface is not available in the framework presently.
+ Presently it offer very minimal kind of interface.
+4. SLIC interface will be enhanced as per Zarlink Open source APIs in future.
diff --git a/Documentation/tdm/tdm-summary.txt b/Documentation/tdm/tdm-summary.txt
new file mode 100644
index 0000000..2788e7f
--- /dev/null
+++ b/Documentation/tdm/tdm-summary.txt
@@ -0,0 +1,103 @@
+Time Division Multiplexing (TDM)
+=================================
+
+TDM stands for Time Division Multiplexing. TDM is a type of digital or analog multiplexing in which two or more bit
+streams or signals are transferred apparently simultaneously as sub-channels
+in one communication channel, but are physically taking turns on the channel.
+
+The time domain is divided into several recurrent timeslots of fixed duration.
+These timeslot are grouped together to form a channel. A sample byte or data
+block of channel 1 is transmitted during timeslots allocated to channel 1,
+channel 2 during timeslot for channel 2, etc.
+
+One TDM frame consists of multiple channels. After the last channel the cycle
+starts all over again with a new frame, starting with the second sample, byte
+or data block from channel 1, and so on.
+
+X----------TDM Frame 0-------------X------TDM Frame 1-----------------X
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+| 0 | 1 | 2 | 3 | 4 | ...| n | 0 | 1 | 2 | 3 | 4 | ...| n |...
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+<----> <---->
+channel 0 channel 0
+-------------------------------------------------------------------->
+ Increasing Time
+
+Physical TDM interface
+=======================
+
+Physically TDM interface is a serial full duplex interface designed to
+communicate with variety of serial devices like industry standard framers,
+codecs, other DSPs, and microprocessors. It is typically used to transfer
+samples in a periodic manner. The TDM consists of independent transmitter and
+receiver sections with independent clock generation and frame synchronization.
+
+External TDM signals are:
+1. TDM_TCK: TDM Transmit clock
+2. TDM_RCK: TDM Receive clock
+3. TDM_TFS: TDM Tx frame sync to identify frame boundary
+4. TDM_RFS: TDM Rx Frame sync to identify frame boundary
+5. TDM_TD: TDM Tx data
+6. TDM_RD: TDM Rx data
+
+TDM is generally used to simultaneously transmit periodic data for multiple
+users. Common use cases would be to carry voice for various telephone
+subscribers in the telephone networks. It is widely used to carry telephonic
+data of various industry standards like E1/T1 data, etc.
+
+T1 Details
+==========
+T1 frame consists of 24 channels of 8 bits each plus one frame alignment bit.
+So a T1 frame has a total of 24x8 + 1 = 193 bits. Since each T1 frame contains
+one byte of voice data for each of 24 channels and the system needs to maintain
+a data rate of 8000 samples/sec. This would require 8000 frames/sec to be sent,
+yielding a data rate of 8000x193 bit/sec = 1.544 Mbps.
+
+E1 Details
+===========
+E1 frame consists of 32 channels each of 8 bits. Thus having a total frame
+length of 32x8 = 256 bits. Similar to the case of T1 it has to maintain a data
+rate of 8000 frames/sec. Thus having a data rate of 8000 x 256 bits/sec =
+2.048 Mbps.
+
+TDM use cases
+=============
+
+With SLIC kind of devices
+=========================
+SLIC stands for Subscriber Line Interface Card.
+Typically TDM systems consist of TDM controller and a line control device.
+
+The TDM controller interfaces to the line control device through TDM interface
+which is digital TDM multiplexed data.
+
+The Line controller has the functionality to interface with the TDM controller
+at one end and interface with the analog units at the other. For example if the
+line control device is a SLIC kind of device.
+The typical setup would be:
+
+|------------------|
+| |
+| | /-------\ |---------|
+| TDM controller |/ TDM \ | SLIC |<--------> s-ch0 analog phone 1
+| |\ data / | |<--------> s-ch1 analog phone 2
+| | \-------/ |---------|<--------> s-ch2 analog phone 3
+| |<----digital----> <analog>
+|------------------|
+
+
+
+Another use case (VoIP):
+========================
+
+ Voice packets on network
+ |--------| |------| _________ |------| |------|
+>----| |/---\| TDM | ( ) | TDM |/---\| |----->
+<----| SLIC |\---/| | ( n/w ) | |\---/| SLIC |-----<
+>----| | |------| --------- |------| | |----->
+ |--------| mux demux |------|
+
+In the above figure analog phones are connected to the hosts via SLICs.
+The voice spoken on the phones is multiplexed converted into VoIP packets
+and sent over network. At the rendering end the multiplexed data
+is de-multiplexed and sent to respective listeners via SLIC.
--
1.7.6.GIT
^ permalink raw reply related
* RE: [PATCH V4] powerpc/85xx: Add machine check handler to fix PCIe erratum on mpc85xx
From: Jia Hongtao-B38951 @ 2013-03-05 10:12 UTC (permalink / raw)
To: Wood Scott-B07421, Stuart Yoder; +Cc: linuxppc-dev@lists.ozlabs.org
In-Reply-To: <1362440744.16575.6@snotra>
> -----Original Message-----
> From: Wood Scott-B07421
> Sent: Tuesday, March 05, 2013 7:46 AM
> To: Stuart Yoder
> Cc: Jia Hongtao-B38951; linuxppc-dev@lists.ozlabs.org; Kumar Gala
> Subject: Re: [PATCH V4] powerpc/85xx: Add machine check handler to fix
> PCIe erratum on mpc85xx
>=20
> On 03/04/2013 10:16:10 AM, Stuart Yoder wrote:
> > On Mon, Mar 4, 2013 at 2:40 AM, Jia Hongtao <B38951@freescale.com>
> > wrote:
> > > A PCIe erratum of mpc85xx may causes a core hang when a link of PCIe
> > > goes down. when the link goes down, Non-posted transactions issued
> > > via the ATMU requiring completion result in an instruction stall.
> > > At the same time a machine-check exception is generated to the core
> > > to allow further processing by the handler. We implements the
> > handler
> > > which skips the instruction caused the stall.
> >
> > Can you explain at a high level how just skipping an instruction
> > solves
> > anything? If you just skip a load/store and continue like nothing is
> > wrong, isn't your system possibly in a really bad state.
>=20
> If the instruction was a load, we probably at least want to fill the
> destination register with 0xffffffff or similar.
You discuss this with Liu Shuo about a year ago.
here is the log:
"
On 02/01/2012 02:18 AM, shuo.liu@freescale.com wrote:
> v3 : Skip the instruction only. Don't access the user space memory in=20
> mechine check.=20
It may be the least bad option for now, but be aware that there's a
small chance that this will cause a leak of sensitive information (such
as a piece of a crypto key that happened to be sitting in the register
to be loaded into).
-Scott
"
-Hongtao.
>=20
> > And if the core is already hung, due to the PCI link going down, isn't
> > it too late? How does skipping help?
>=20
> Maybe the machine check unhangs the core?
>=20
> Is there an erratum number for this?
>=20
> -Scott
^ permalink raw reply
* RE: [PATCH V4] powerpc/85xx: Add machine check handler to fix PCIe erratum on mpc85xx
From: Jia Hongtao-B38951 @ 2013-03-05 10:12 UTC (permalink / raw)
To: David Laight, linuxppc-dev@lists.ozlabs.org,
galak@kernel.crashing.org
Cc: Wood Scott-B07421
In-Reply-To: <AE90C24D6B3A694183C094C60CF0A2F6026B7173@saturn3.aculab.com>
> -----Original Message-----
> From: David Laight [mailto:David.Laight@ACULAB.COM]
> Sent: Tuesday, March 05, 2013 1:16 AM
> To: Jia Hongtao-B38951; linuxppc-dev@lists.ozlabs.org;
> galak@kernel.crashing.org
> Cc: Wood Scott-B07421
> Subject: RE: [PATCH V4] powerpc/85xx: Add machine check handler to fix
> PCIe erratum on mpc85xx
>=20
> > A PCIe erratum of mpc85xx may causes a core hang when a link of PCIe
> > goes down. when the link goes down, Non-posted transactions issued via
> > the ATMU requiring completion result in an instruction stall.
> > At the same time a machine-check exception is generated to the core to
> > allow further processing by the handler. We implements the handler
> > which skips the instruction caused the stall.
>=20
> Just skipping the instruction doesn't seem a good idea.
> But I suspect that re-initialising the PCI interface is also almost
> impossible.
This *skipping* is the best way I thought for this errata.
It's not perfect but works.
-Hongtao.
>=20
> Does the mpc83xx have the same errata?
> We've seen machine-check faults using the CSB bridge on an 83xx doing a
> 'pio' access after a PEX_DMA transfer to certain target addresses stalls
> - software gives up waiting for the dma.
> The target is an fpga, nothing is mapped at those addesses - but we'd
> expect to get ~0u back as happens on other slave windows.
>=20
> I also remember some problems with single word DMA.
>=20
> David
>=20
>=20
^ permalink raw reply
* Re: [PATCH] drivers/tty/hvc: using strlcpy instead of strncpy
From: Jiri Slaby @ 2013-03-05 9:36 UTC (permalink / raw)
To: Chen Gang; +Cc: Greg KH, tklauser, wfp5p, linuxppc-dev, alan
In-Reply-To: <51355142.4070505@asianux.com>
On 03/05/2013 02:58 AM, Chen Gang wrote:
> 于 2013年02月28日 21:47, Jiri Slaby 写道:
>>>> when strlen(&pi->location_code[0]) == HVCS_CLC_LENGTH + 2
>> It cannot, pi->location_code is defined as char[HVCS_CLC_LENGTH + 1].
>>
>
> really, it is, I did not notice it.
>
> but I still prefer to modify it, but the patch should be changed
> such as:
> subject: beautify code: deleting useless judging code.
> comments: src buf len and dest buf len are the same, strcpy is better.
> contents: using strcpy instead of strncpy, and delete judging code.
>
> is it ok ?
Yeah.
> BTW:
> sorry for my reply is too late, and did not notify it, originally before.
> I have to do some urgent things, during these days.
> my father had a serious heart disease, and is in hospital.
No problem, these drivers are not so critical. Neither these code paths
in them. Take care of your relatives first.
--
js
suse labs
^ permalink raw reply
* Re: [PATCH 2/3] irq: Add hw continuous IRQs map to virtual continuous IRQs support
From: Mike Qiu @ 2013-03-05 7:44 UTC (permalink / raw)
To: Paul Mundt; +Cc: tglx, linuxppc-dev, linux-kernel
In-Reply-To: <20130305024110.GB14275@linux-sh.org>
于 2013/3/5 10:41, Paul Mundt 写道:
> On Tue, Jan 15, 2013 at 03:38:55PM +0800, Mike Qiu wrote:
>> Adding a function irq_create_mapping_many() which can associate
>> multiple MSIs to a continous irq mapping.
>>
>> This is needed to enable multiple MSI support for pSeries.
>>
>> +int irq_create_mapping_many(struct irq_domain *domain,
>> + irq_hw_number_t hwirq_base, int count)
>> +{
> Other than the other review comments already made, I think you can
> simplify this considerably by simply doing what irq_create_strict_mappings() does,
> and relaxing the irq_base requirements.
>
> In any event, as you are creating a new interface, I don't think you want
> to carry around half of the legacy crap that irq_create_mapping() has to
> deal with. We made the decision to avoid this with irq_create_strict_mappings()
> intentionally, too.
>
Oh, yes, you are right, I will send out V2 of my patch to make it more
comfortable , and hope you can review my patch again
Thanks
Mike
^ permalink raw reply
* Re: [PATCH 2/3] irq: Add hw continuous IRQs map to virtual continuous IRQs support
From: Mike Qiu @ 2013-03-05 7:19 UTC (permalink / raw)
To: Michael Ellerman; +Cc: tglx, linuxppc-dev, linux-kernel
In-Reply-To: <20130305022348.GB7656@concordia>
于 2013/3/5 10:23, Michael Ellerman 写道:
> On Tue, Jan 15, 2013 at 03:38:55PM +0800, Mike Qiu wrote:
>> Adding a function irq_create_mapping_many() which can associate
>> multiple MSIs to a continous irq mapping.
>>
>> This is needed to enable multiple MSI support for pSeries.
>>
>> Signed-off-by: Mike Qiu <qiudayu@linux.vnet.ibm.com>
>> ---
>> include/linux/irq.h | 2 +
>> include/linux/irqdomain.h | 3 ++
>> kernel/irq/irqdomain.c | 61 +++++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 66 insertions(+), 0 deletions(-)
>>
>> diff --git a/include/linux/irq.h b/include/linux/irq.h
>> index 60ef45b..e00a7ec 100644
>> --- a/include/linux/irq.h
>> +++ b/include/linux/irq.h
>> @@ -592,6 +592,8 @@ int __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,
>> #define irq_alloc_desc_from(from, node) \
>> irq_alloc_descs(-1, from, 1, node)
>>
>> +#define irq_alloc_desc_n(nevc, node) \
>> + irq_alloc_descs(-1, 0, nevc, node)
> This has been superseeded by irq_alloc_descs_from(), which is the right
> way to do it.
Yes, but irq_alloc_descs_from() just for 1 irq, and if I change the api,
maybe a lot places which call this
function will be affact.
>
>> diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h
>> index 0d5b17b..831dded 100644
>> --- a/include/linux/irqdomain.h
>> +++ b/include/linux/irqdomain.h
>> @@ -168,6 +168,9 @@ extern int irq_create_strict_mappings(struct irq_domain *domain,
>> unsigned int irq_base,
>> irq_hw_number_t hwirq_base, int count);
>>
>> +extern int irq_create_mapping_many(struct irq_domain *domain,
>> + irq_hw_number_t hwirq_base, int count);
>> +
>> static inline int irq_create_identity_mapping(struct irq_domain *host,
>> irq_hw_number_t hwirq)
>> {
>> diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
>> index 96f3a1d..38648e6 100644
>> --- a/kernel/irq/irqdomain.c
>> +++ b/kernel/irq/irqdomain.c
>> @@ -636,6 +636,67 @@ int irq_create_strict_mappings(struct irq_domain *domain, unsigned int irq_base,
>> }
>> EXPORT_SYMBOL_GPL(irq_create_strict_mappings);
>>
>> +/**
>> + * irq_create_mapping_many - Map a range of hw IRQs to a range of virtual IRQs
>> + * @domain: domain owning the interrupt range
>> + * @hwirq_base: beginning of continuous hardware IRQ range
>> + * @count: Number of interrupts to map
> For multiple-MSI the allocated interrupt numbers must be a power-of-2,
> and must be naturally aligned. I don't /think/ that's a requirement for
> the virtual numbers, but it's probably best that we do it anyway.
>
> So this API needs to specify that it will give you back a power-of-2
> block that is naturally aligned - otherwise you can't use it for MSI.
rtas_call will return the numbers of hardware interrupt, and it should
be power-of-2,
as this I think do not need to specify
>> + * This routine is used for allocating and mapping a range of hardware
>> + * irqs to virtual IRQs where the virtual irq numbers are not at pre-defined
>> + * locations.
> This comment doesn't make sense to me.
>
>> + *
>> + * Greater than 0 is returned upon success, while any failure to establish a
>> + * static mapping is treated as an error.
>> + */
>> +int irq_create_mapping_many(struct irq_domain *domain,
>> + irq_hw_number_t hwirq_base, int count)
>> +{
>> + int ret, irq_base;
>> + int virq, i;
>> +
>> + pr_debug("irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq_base);
>
> I'd like to see this whole function rewritten to reduce the duplication
> vs irq_create_mapping(). I don't see any reason why this can't be the
> core routine, and irq_create_mapping() becomes a caller of it, passing a
> count of 1 ?
It's good suggestion.
>> + /* Look for default domain if nececssary */
>> + if (!domain)
>> + domain = irq_default_domain;
>> + if (!domain) {
>> + pr_warn("irq_create_mapping called for NULL domain, hwirq=%lx\n"
>> + , hwirq_base);
>> + WARN_ON(1);
>> + return 0;
>> + }
>> + pr_debug("-> using domain @%p\n", domain);
>> +
>> + /* For IRQ_DOMAIN_MAP_LEGACY, get the first virtual interrupt number */
>> + if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY)
>> + return irq_domain_legacy_revmap(domain, hwirq_base);
> The above doesn't work.
Why it doesn't work ?
>> + /* Check if mapping already exists */
>> + for (i = 0; i < count; i++) {
>> + virq = irq_find_mapping(domain, hwirq_base+i);
>> + if (virq) {
>> + pr_debug("existing mapping on virq %d,"
>> + " now dispose it first\n", virq);
>> + irq_dispose_mapping(virq);
> You might have just disposed of someone elses mapping, we shouldn't do
> that. It should be an error to the caller.
It's a good question. If the interrupt used for someone elses, why I can
apply it from the system?
So it may someone else forget to dispose mapping, and it never be used
for others as I have got
the interrupt I think.
> cheers
>
^ permalink raw reply
* [PATCH] powerpc/powernv: Fix next available MSI IRQ
From: Gavin Shan @ 2013-03-05 6:59 UTC (permalink / raw)
To: linuxppc-dev; +Cc: Gavin Shan
The allocation of MSI is implemented based on bitmap and working
like the mechanism of strict round through the traced next available
cursor. However, the next available MSI is never updated in current
implementation. The patch fixes the issue.
Signed-off-by: Gavin Shan <shangw@linux.vnet.ibm.com>
---
arch/powerpc/platforms/powernv/pci.c | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
diff --git a/arch/powerpc/platforms/powernv/pci.c b/arch/powerpc/platforms/powernv/pci.c
index 6f464dc..9cf18c4 100644
--- a/arch/powerpc/platforms/powernv/pci.c
+++ b/arch/powerpc/platforms/powernv/pci.c
@@ -66,6 +66,11 @@ static unsigned int pnv_get_one_msi(struct pnv_phb *phb)
rc = 0;
goto out;
}
+
+ if (id >= phb->msi_count - 1)
+ phb->msi_next = 0;
+ else
+ phb->msi_next = id + 1;
__set_bit(id, phb->msi_map);
rc = id + phb->msi_base;
out:
--
1.7.5.4
^ permalink raw reply related
* [git pull] Please pull powerpc.git merge branch
From: Benjamin Herrenschmidt @ 2013-03-05 6:30 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linuxppc-dev, Linux Kernel list
Hi Linus !
Here are a few powerpc bits & fixes for rc1. A couple of str*cpy fixes,
some fixes in handling the FSCR register on Power8 (controls the
enabling of processor features), a 32-bit build fix and a couple more
nits.
Cheers,
Ben.
The following changes since commit 6dbe51c251a327e012439c4772097a13df43c5b8:
Linux 3.9-rc1 (2013-03-03 15:11:05 -0800)
are available in the git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git merge
for you to fetch changes up to 54c9b2253d34e8998e4bff9ac2d7a3ba0b861d52:
powerpc: Set DSCR bit in FSCR setup (2013-03-05 16:56:30 +1100)
----------------------------------------------------------------
Akinobu Mita (1):
powerpc: Remove unused BITOP_LE_SWIZZLE macro
Chen Gang (2):
powerpc/pseries/hvcserver: Fix strncpy buffer limit in location code
drivers/tty/hvc: Use strlcpy instead of strncpy
Michael Neuling (4):
powerpc: Avoid link stack corruption in MMU on syscall entry path
powerpc: Fix setting FSCR for HV=0 and on secondary CPUs
powerpc: Add DSCR FSCR register bit definition
powerpc: Set DSCR bit in FSCR setup
Tony Breeds (2):
powerpc: Fix compile of sha1-powerpc-asm.S on 32-bit
powerpc: Wireup the kcmp syscall to sys_ni
arch/powerpc/crypto/sha1-powerpc-asm.S | 4 ++--
arch/powerpc/include/asm/bitops.h | 2 --
arch/powerpc/include/asm/reg.h | 3 ++-
arch/powerpc/include/asm/systbl.h | 1 +
arch/powerpc/include/asm/unistd.h | 2 +-
arch/powerpc/include/uapi/asm/unistd.h | 1 +
arch/powerpc/kernel/cpu_setup_power.S | 5 +++--
arch/powerpc/kernel/exceptions-64s.S | 4 ++--
arch/powerpc/platforms/pseries/hvcserver.c | 5 +++--
drivers/tty/hvc/hvcs.c | 9 ++-------
10 files changed, 17 insertions(+), 19 deletions(-)
^ permalink raw reply
* [PATCH 3/3] powerpc: Set DSCR bit in FSCR setup
From: Michael Neuling @ 2013-03-05 5:45 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: Michael Neuling, linuxppc-dev, Ian Munsie
In-Reply-To: <1362462352-12009-1-git-send-email-mikey@neuling.org>
We support DSCR (Data Stream Control Register) so we should make sure we set it
in the FSCR (Facility Status & Control Register) incase some firmwares don't
set it. If we don't set this, we'll take a facility unavailable exception when
using the DSCR.
Signed-off-by: Michael Neuling <mikey@neuling.org>
---
arch/powerpc/kernel/cpu_setup_power.S | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/powerpc/kernel/cpu_setup_power.S b/arch/powerpc/kernel/cpu_setup_power.S
index bb2d203..ea847ab 100644
--- a/arch/powerpc/kernel/cpu_setup_power.S
+++ b/arch/powerpc/kernel/cpu_setup_power.S
@@ -116,7 +116,7 @@ __init_LPCR:
__init_FSCR:
mfspr r3,SPRN_FSCR
- ori r3,r3,FSCR_TAR
+ ori r3,r3,FSCR_TAR|FSCR_DSCR
mtspr SPRN_FSCR,r3
blr
--
1.7.10.4
^ permalink raw reply related
* [PATCH 2/3] powerpc: Add DSCR FSCR register bit definition
From: Michael Neuling @ 2013-03-05 5:45 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: Michael Neuling, linuxppc-dev, Ian Munsie
In-Reply-To: <1362462352-12009-1-git-send-email-mikey@neuling.org>
This sets the DSCR (Data Stream Control Register) in the FSCR (Facility Status
& Control Register).
Also harmonise TAR (Target Address Register) FSCR bit definition too.
Signed-off-by: Michael Neuling <mikey@neuling.org>
---
arch/powerpc/include/asm/reg.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/include/asm/reg.h b/arch/powerpc/include/asm/reg.h
index e665861..c9c67fc 100644
--- a/arch/powerpc/include/asm/reg.h
+++ b/arch/powerpc/include/asm/reg.h
@@ -266,7 +266,8 @@
#define SPRN_HSRR0 0x13A /* Hypervisor Save/Restore 0 */
#define SPRN_HSRR1 0x13B /* Hypervisor Save/Restore 1 */
#define SPRN_FSCR 0x099 /* Facility Status & Control Register */
-#define FSCR_TAR (1<<8) /* Enable Target Adress Register */
+#define FSCR_TAR (1 << (63-55)) /* Enable Target Address Register */
+#define FSCR_DSCR (1 << (63-61)) /* Enable Data Stream Control Register */
#define SPRN_TAR 0x32f /* Target Address Register */
#define SPRN_LPCR 0x13E /* LPAR Control Register */
#define LPCR_VPM0 (1ul << (63-0))
--
1.7.10.4
^ permalink raw reply related
* [PATCH 1/3] powerpc: Fix setting FSCR for HV=0 and on secondary CPUs
From: Michael Neuling @ 2013-03-05 5:45 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: Michael Neuling, linuxppc-dev, Ian Munsie
In-Reply-To: <1362462352-12009-1-git-send-email-mikey@neuling.org>
Currently we only set the FSCR (Facility Status and Control Register) when HV=1
but this feature is available when HV=0 also. This patch sets FSCR when HV=0.
Also, we currently only set the FSCR on the master CPU. This patch also sets
the FSCR on secondary CPUs.
Signed-off-by: Michael Neuling <mikey@neuling.org>
cc: Ian Munsie <imunsie@au1.ibm.com>
---
arch/powerpc/kernel/cpu_setup_power.S | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/kernel/cpu_setup_power.S b/arch/powerpc/kernel/cpu_setup_power.S
index d29facb..bb2d203 100644
--- a/arch/powerpc/kernel/cpu_setup_power.S
+++ b/arch/powerpc/kernel/cpu_setup_power.S
@@ -48,6 +48,7 @@ _GLOBAL(__restore_cpu_power7)
_GLOBAL(__setup_cpu_power8)
mflr r11
+ bl __init_FSCR
bl __init_hvmode_206
mtlr r11
beqlr
@@ -56,13 +57,13 @@ _GLOBAL(__setup_cpu_power8)
mfspr r3,SPRN_LPCR
oris r3, r3, LPCR_AIL_3@h
bl __init_LPCR
- bl __init_FSCR
bl __init_TLB
mtlr r11
blr
_GLOBAL(__restore_cpu_power8)
mflr r11
+ bl __init_FSCR
mfmsr r3
rldicl. r0,r3,4,63
beqlr
--
1.7.10.4
^ permalink raw reply related
* [PATCH 0/3] powerpc: FSCR fixes for POWER8
From: Michael Neuling @ 2013-03-05 5:45 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: Michael Neuling, linuxppc-dev, Ian Munsie
In-Reply-To: <1362459996.21357.46.camel@pasglop>
Benh,
Here are a few fixes for the POWER8 (Performance Optimization With Enhanced
RISC Eight) FSCR (Facility Status & Control Register).
First patch changes the FSCR so that it's set on secondary CPUs as well as when
MSR HV=0.
Second two patches make sure that the FSCR DSCR (Data Stream Control
Register) bit is set so that we don't trap on DSCR usage.
These are aimed to fix issues in 3.9.
v2:
Addressing comments from benh (Benjamin Herrenschmidt)
Mikey
^ permalink raw reply
* Re: [PATCH 2/3] powerpc: Add DSCR FSCR register bit definition
From: Benjamin Herrenschmidt @ 2013-03-05 5:09 UTC (permalink / raw)
To: Michael Neuling; +Cc: linuxppc-dev, Ian Munsie
In-Reply-To: <1362390402-17725-3-git-send-email-mikey@neuling.org>
On Mon, 2013-03-04 at 20:46 +1100, Michael Neuling wrote:
> Also harmonise TAR bit definition too.
Same, expand accronyms, minimum blurb about what these are about (not a
long explanation, just so that when I come out of the blue I can at
least connect it to something that makes sense :-)
Cheers,
Ben.
^ permalink raw reply
* Re: [PATCH 1/3] powerpc: Fix setting FSCR for HV=0 and secondary CPUs
From: Benjamin Herrenschmidt @ 2013-03-05 5:06 UTC (permalink / raw)
To: Michael Neuling; +Cc: linuxppc-dev, Ian Munsie
In-Reply-To: <1362390402-17725-2-git-send-email-mikey@neuling.org>
On Mon, 2013-03-04 at 20:46 +1100, Michael Neuling wrote:
> Currently we only set the FSCR when HV=1 but this feature is available when
> HV=0 also. This patch sets FSCR when HV=0.
>
> Also, we currently only set the FSCR on the master CPU. This patch also sets
> the FSCR on secondary CPUs.
Please add a quick blurb/reminder of what FSCR is (at least expand the
accronym).
Cheers,
Ben.
> Signed-off-by: Michael Neuling <mikey@neuling.org>
> cc: Ian Munsie <imunsie@au1.ibm.com>
> ---
> arch/powerpc/kernel/cpu_setup_power.S | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/arch/powerpc/kernel/cpu_setup_power.S b/arch/powerpc/kernel/cpu_setup_power.S
> index d29facb..bb2d203 100644
> --- a/arch/powerpc/kernel/cpu_setup_power.S
> +++ b/arch/powerpc/kernel/cpu_setup_power.S
> @@ -48,6 +48,7 @@ _GLOBAL(__restore_cpu_power7)
>
> _GLOBAL(__setup_cpu_power8)
> mflr r11
> + bl __init_FSCR
> bl __init_hvmode_206
> mtlr r11
> beqlr
> @@ -56,13 +57,13 @@ _GLOBAL(__setup_cpu_power8)
> mfspr r3,SPRN_LPCR
> oris r3, r3, LPCR_AIL_3@h
> bl __init_LPCR
> - bl __init_FSCR
> bl __init_TLB
> mtlr r11
> blr
>
> _GLOBAL(__restore_cpu_power8)
> mflr r11
> + bl __init_FSCR
> mfmsr r3
> rldicl. r0,r3,4,63
> beqlr
^ permalink raw reply
* Re: [PATCH 2/3] irq: Add hw continuous IRQs map to virtual continuous IRQs support
From: Paul Mundt @ 2013-03-05 2:41 UTC (permalink / raw)
To: Mike Qiu; +Cc: tglx, linuxppc-dev, linux-kernel
In-Reply-To: <1358235536-32741-3-git-send-email-qiudayu@linux.vnet.ibm.com>
On Tue, Jan 15, 2013 at 03:38:55PM +0800, Mike Qiu wrote:
> Adding a function irq_create_mapping_many() which can associate
> multiple MSIs to a continous irq mapping.
>
> This is needed to enable multiple MSI support for pSeries.
>
> +int irq_create_mapping_many(struct irq_domain *domain,
> + irq_hw_number_t hwirq_base, int count)
> +{
Other than the other review comments already made, I think you can
simplify this considerably by simply doing what irq_create_strict_mappings() does,
and relaxing the irq_base requirements.
In any event, as you are creating a new interface, I don't think you want
to carry around half of the legacy crap that irq_create_mapping() has to
deal with. We made the decision to avoid this with irq_create_strict_mappings()
intentionally, too.
^ permalink raw reply
* Re: [PATCH 2/3] irq: Add hw continuous IRQs map to virtual continuous IRQs support
From: Michael Ellerman @ 2013-03-05 2:23 UTC (permalink / raw)
To: Mike Qiu; +Cc: tglx, linuxppc-dev, linux-kernel
In-Reply-To: <1358235536-32741-3-git-send-email-qiudayu@linux.vnet.ibm.com>
On Tue, Jan 15, 2013 at 03:38:55PM +0800, Mike Qiu wrote:
> Adding a function irq_create_mapping_many() which can associate
> multiple MSIs to a continous irq mapping.
>
> This is needed to enable multiple MSI support for pSeries.
>
> Signed-off-by: Mike Qiu <qiudayu@linux.vnet.ibm.com>
> ---
> include/linux/irq.h | 2 +
> include/linux/irqdomain.h | 3 ++
> kernel/irq/irqdomain.c | 61 +++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 66 insertions(+), 0 deletions(-)
>
> diff --git a/include/linux/irq.h b/include/linux/irq.h
> index 60ef45b..e00a7ec 100644
> --- a/include/linux/irq.h
> +++ b/include/linux/irq.h
> @@ -592,6 +592,8 @@ int __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,
> #define irq_alloc_desc_from(from, node) \
> irq_alloc_descs(-1, from, 1, node)
>
> +#define irq_alloc_desc_n(nevc, node) \
> + irq_alloc_descs(-1, 0, nevc, node)
This has been superseeded by irq_alloc_descs_from(), which is the right
way to do it.
> diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h
> index 0d5b17b..831dded 100644
> --- a/include/linux/irqdomain.h
> +++ b/include/linux/irqdomain.h
> @@ -168,6 +168,9 @@ extern int irq_create_strict_mappings(struct irq_domain *domain,
> unsigned int irq_base,
> irq_hw_number_t hwirq_base, int count);
>
> +extern int irq_create_mapping_many(struct irq_domain *domain,
> + irq_hw_number_t hwirq_base, int count);
> +
> static inline int irq_create_identity_mapping(struct irq_domain *host,
> irq_hw_number_t hwirq)
> {
> diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
> index 96f3a1d..38648e6 100644
> --- a/kernel/irq/irqdomain.c
> +++ b/kernel/irq/irqdomain.c
> @@ -636,6 +636,67 @@ int irq_create_strict_mappings(struct irq_domain *domain, unsigned int irq_base,
> }
> EXPORT_SYMBOL_GPL(irq_create_strict_mappings);
>
> +/**
> + * irq_create_mapping_many - Map a range of hw IRQs to a range of virtual IRQs
> + * @domain: domain owning the interrupt range
> + * @hwirq_base: beginning of continuous hardware IRQ range
> + * @count: Number of interrupts to map
For multiple-MSI the allocated interrupt numbers must be a power-of-2,
and must be naturally aligned. I don't /think/ that's a requirement for
the virtual numbers, but it's probably best that we do it anyway.
So this API needs to specify that it will give you back a power-of-2
block that is naturally aligned - otherwise you can't use it for MSI.
> + * This routine is used for allocating and mapping a range of hardware
> + * irqs to virtual IRQs where the virtual irq numbers are not at pre-defined
> + * locations.
This comment doesn't make sense to me.
> + *
> + * Greater than 0 is returned upon success, while any failure to establish a
> + * static mapping is treated as an error.
> + */
> +int irq_create_mapping_many(struct irq_domain *domain,
> + irq_hw_number_t hwirq_base, int count)
> +{
> + int ret, irq_base;
> + int virq, i;
> +
> + pr_debug("irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq_base);
I'd like to see this whole function rewritten to reduce the duplication
vs irq_create_mapping(). I don't see any reason why this can't be the
core routine, and irq_create_mapping() becomes a caller of it, passing a
count of 1 ?
> + /* Look for default domain if nececssary */
> + if (!domain)
> + domain = irq_default_domain;
> + if (!domain) {
> + pr_warn("irq_create_mapping called for NULL domain, hwirq=%lx\n"
> + , hwirq_base);
> + WARN_ON(1);
> + return 0;
> + }
> + pr_debug("-> using domain @%p\n", domain);
> +
> + /* For IRQ_DOMAIN_MAP_LEGACY, get the first virtual interrupt number */
> + if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY)
> + return irq_domain_legacy_revmap(domain, hwirq_base);
The above doesn't work.
> + /* Check if mapping already exists */
> + for (i = 0; i < count; i++) {
> + virq = irq_find_mapping(domain, hwirq_base+i);
> + if (virq) {
> + pr_debug("existing mapping on virq %d,"
> + " now dispose it first\n", virq);
> + irq_dispose_mapping(virq);
You might have just disposed of someone elses mapping, we shouldn't do
that. It should be an error to the caller.
cheers
^ permalink raw reply
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