Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v12 10/11] seccomp: allow mode setting across threads
From: Kees Cook @ 2014-07-17 18:08 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405620518-18495-1-git-send-email-keescook@chromium.org>

This changes the mode setting helper to allow threads to change the
seccomp mode from another thread. We must maintain barriers to keep
TIF_SECCOMP synchronized with the rest of the seccomp state.

Signed-off-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Reviewed-by: Andy Lutomirski <luto@amacapital.net>
---
 kernel/seccomp.c |   36 +++++++++++++++++++++++++-----------
 1 file changed, 25 insertions(+), 11 deletions(-)

diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index d5543e787e4e..9065d2c79c56 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -173,21 +173,24 @@ static int seccomp_check_filter(struct sock_filter *filter, unsigned int flen)
  */
 static u32 seccomp_run_filters(int syscall)
 {
-	struct seccomp_filter *f;
+	struct seccomp_filter *f = ACCESS_ONCE(current->seccomp.filter);
 	struct seccomp_data sd;
 	u32 ret = SECCOMP_RET_ALLOW;
 
 	/* Ensure unexpected behavior doesn't result in failing open. */
-	if (WARN_ON(current->seccomp.filter == NULL))
+	if (unlikely(WARN_ON(f == NULL)))
 		return SECCOMP_RET_KILL;
 
+	/* Make sure cross-thread synced filter points somewhere sane. */
+	smp_read_barrier_depends();
+
 	populate_seccomp_data(&sd);
 
 	/*
 	 * All filters in the list are evaluated and the lowest BPF return
 	 * value always takes priority (ignoring the DATA).
 	 */
-	for (f = current->seccomp.filter; f; f = f->prev) {
+	for (; f; f = f->prev) {
 		u32 cur_ret = SK_RUN_FILTER(f->prog, (void *)&sd);
 
 		if ((cur_ret & SECCOMP_RET_ACTION) < (ret & SECCOMP_RET_ACTION))
@@ -207,12 +210,18 @@ static inline bool seccomp_may_assign_mode(unsigned long seccomp_mode)
 	return true;
 }
 
-static inline void seccomp_assign_mode(unsigned long seccomp_mode)
+static inline void seccomp_assign_mode(struct task_struct *task,
+				       unsigned long seccomp_mode)
 {
-	BUG_ON(!spin_is_locked(&current->sighand->siglock));
+	BUG_ON(!spin_is_locked(&task->sighand->siglock));
 
-	current->seccomp.mode = seccomp_mode;
-	set_tsk_thread_flag(current, TIF_SECCOMP);
+	task->seccomp.mode = seccomp_mode;
+	/*
+	 * Make sure TIF_SECCOMP cannot be set before the mode (and
+	 * filter) is set.
+	 */
+	smp_mb__before_atomic();
+	set_tsk_thread_flag(task, TIF_SECCOMP);
 }
 
 #ifdef CONFIG_SECCOMP_FILTER
@@ -435,12 +444,17 @@ static int mode1_syscalls_32[] = {
 
 int __secure_computing(int this_syscall)
 {
-	int mode = current->seccomp.mode;
 	int exit_sig = 0;
 	int *syscall;
 	u32 ret;
 
-	switch (mode) {
+	/*
+	 * Make sure that any changes to mode from another thread have
+	 * been seen after TIF_SECCOMP was seen.
+	 */
+	rmb();
+
+	switch (current->seccomp.mode) {
 	case SECCOMP_MODE_STRICT:
 		syscall = mode1_syscalls;
 #ifdef CONFIG_COMPAT
@@ -545,7 +559,7 @@ static long seccomp_set_mode_strict(void)
 #ifdef TIF_NOTSC
 	disable_TSC();
 #endif
-	seccomp_assign_mode(seccomp_mode);
+	seccomp_assign_mode(current, seccomp_mode);
 	ret = 0;
 
 out:
@@ -595,7 +609,7 @@ static long seccomp_set_mode_filter(unsigned int flags,
 	/* Do not free the successfully attached filter. */
 	prepared = NULL;
 
-	seccomp_assign_mode(seccomp_mode);
+	seccomp_assign_mode(current, seccomp_mode);
 out:
 	spin_unlock_irq(&current->sighand->siglock);
 	seccomp_filter_free(prepared);
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH v12 11/11] seccomp: implement SECCOMP_FILTER_FLAG_TSYNC
From: Kees Cook @ 2014-07-17 18:08 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405620518-18495-1-git-send-email-keescook@chromium.org>

Applying restrictive seccomp filter programs to large or diverse
codebases often requires handling threads which may be started early in
the process lifetime (e.g., by code that is linked in). While it is
possible to apply permissive programs prior to process start up, it is
difficult to further restrict the kernel ABI to those threads after that
point.

This change adds a new seccomp syscall flag to SECCOMP_SET_MODE_FILTER for
synchronizing thread group seccomp filters at filter installation time.

When calling seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC,
filter) an attempt will be made to synchronize all threads in current's
threadgroup to its new seccomp filter program. This is possible iff all
threads are using a filter that is an ancestor to the filter current is
attempting to synchronize to. NULL filters (where the task is running as
SECCOMP_MODE_NONE) are also treated as ancestors allowing threads to be
transitioned into SECCOMP_MODE_FILTER. If prctrl(PR_SET_NO_NEW_PRIVS,
...) has been set on the calling thread, no_new_privs will be set for
all synchronized threads too. On success, 0 is returned. On failure,
the pid of one of the failing threads will be returned and no filters
will have been applied.

The race conditions against another thread are:
- requesting TSYNC (already handled by sighand lock)
- performing a clone (already handled by sighand lock)
- changing its filter (already handled by sighand lock)
- calling exec (handled by cred_guard_mutex)
The clone case is assisted by the fact that new threads will have their
seccomp state duplicated from their parent before appearing on the tasklist.

Holding cred_guard_mutex means that seccomp filters cannot be assigned
while in the middle of another thread's exec (potentially bypassing
no_new_privs or similar). The call to de_thread() may kill threads waiting
for the mutex.

Changes across threads to the filter pointer includes a barrier.

Based on patches by Will Drewry.

Suggested-by: Julien Tinnes <jln@chromium.org>
Signed-off-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Reviewed-by: Andy Lutomirski <luto@amacapital.net>
---
 fs/exec.c                    |    2 +-
 include/linux/seccomp.h      |    2 +
 include/uapi/linux/seccomp.h |    3 +
 kernel/seccomp.c             |  135 +++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 140 insertions(+), 2 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index 0f5c272410f6..ab1f1200ce5d 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1216,7 +1216,7 @@ EXPORT_SYMBOL(install_exec_creds);
 /*
  * determine how safe it is to execute the proposed program
  * - the caller must hold ->cred_guard_mutex to protect against
- *   PTRACE_ATTACH
+ *   PTRACE_ATTACH or seccomp thread-sync
  */
 static void check_unsafe_exec(struct linux_binprm *bprm)
 {
diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h
index 9ff98b4bfe2e..5d586a45a319 100644
--- a/include/linux/seccomp.h
+++ b/include/linux/seccomp.h
@@ -3,6 +3,8 @@
 
 #include <uapi/linux/seccomp.h>
 
+#define SECCOMP_FILTER_FLAG_MASK	(SECCOMP_FILTER_FLAG_TSYNC)
+
 #ifdef CONFIG_SECCOMP
 
 #include <linux/thread_info.h>
diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h
index b258878ba754..0f238a43ff1e 100644
--- a/include/uapi/linux/seccomp.h
+++ b/include/uapi/linux/seccomp.h
@@ -14,6 +14,9 @@
 #define SECCOMP_SET_MODE_STRICT	0
 #define SECCOMP_SET_MODE_FILTER	1
 
+/* Valid flags for SECCOMP_SET_MODE_FILTER */
+#define SECCOMP_FILTER_FLAG_TSYNC	1
+
 /*
  * All BPF programs must return a 32-bit value.
  * The bottom 16-bits are for optional return data.
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 9065d2c79c56..74f460179171 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -26,6 +26,7 @@
 #ifdef CONFIG_SECCOMP_FILTER
 #include <asm/syscall.h>
 #include <linux/filter.h>
+#include <linux/pid.h>
 #include <linux/ptrace.h>
 #include <linux/security.h>
 #include <linux/tracehook.h>
@@ -225,6 +226,114 @@ static inline void seccomp_assign_mode(struct task_struct *task,
 }
 
 #ifdef CONFIG_SECCOMP_FILTER
+/* Returns 1 if the parent is an ancestor of the child. */
+static int is_ancestor(struct seccomp_filter *parent,
+		       struct seccomp_filter *child)
+{
+	/* NULL is the root ancestor. */
+	if (parent == NULL)
+		return 1;
+	for (; child; child = child->prev)
+		if (child == parent)
+			return 1;
+	return 0;
+}
+
+/**
+ * seccomp_can_sync_threads: checks if all threads can be synchronized
+ *
+ * Expects sighand and cred_guard_mutex locks to be held.
+ *
+ * Returns 0 on success, -ve on error, or the pid of a thread which was
+ * either not in the correct seccomp mode or it did not have an ancestral
+ * seccomp filter.
+ */
+static inline pid_t seccomp_can_sync_threads(void)
+{
+	struct task_struct *thread, *caller;
+
+	BUG_ON(!mutex_is_locked(&current->signal->cred_guard_mutex));
+	BUG_ON(!spin_is_locked(&current->sighand->siglock));
+
+	/* Validate all threads being eligible for synchronization. */
+	caller = current;
+	for_each_thread(caller, thread) {
+		pid_t failed;
+
+		/* Skip current, since it is initiating the sync. */
+		if (thread == caller)
+			continue;
+
+		if (thread->seccomp.mode == SECCOMP_MODE_DISABLED ||
+		    (thread->seccomp.mode == SECCOMP_MODE_FILTER &&
+		     is_ancestor(thread->seccomp.filter,
+				 caller->seccomp.filter)))
+			continue;
+
+		/* Return the first thread that cannot be synchronized. */
+		failed = task_pid_vnr(thread);
+		/* If the pid cannot be resolved, then return -ESRCH */
+		if (unlikely(WARN_ON(failed == 0)))
+			failed = -ESRCH;
+		return failed;
+	}
+
+	return 0;
+}
+
+/**
+ * seccomp_sync_threads: sets all threads to use current's filter
+ *
+ * Expects sighand and cred_guard_mutex locks to be held, and for
+ * seccomp_can_sync_threads() to have returned success already
+ * without dropping the locks.
+ *
+ */
+static inline void seccomp_sync_threads(void)
+{
+	struct task_struct *thread, *caller;
+
+	BUG_ON(!mutex_is_locked(&current->signal->cred_guard_mutex));
+	BUG_ON(!spin_is_locked(&current->sighand->siglock));
+
+	/* Synchronize all threads. */
+	caller = current;
+	for_each_thread(caller, thread) {
+		/* Skip current, since it needs no changes. */
+		if (thread == caller)
+			continue;
+
+		/* Get a task reference for the new leaf node. */
+		get_seccomp_filter(caller);
+		/*
+		 * Drop the task reference to the shared ancestor since
+		 * current's path will hold a reference.  (This also
+		 * allows a put before the assignment.)
+		 */
+		put_seccomp_filter(thread);
+		smp_store_release(&thread->seccomp.filter,
+				  caller->seccomp.filter);
+		/*
+		 * Opt the other thread into seccomp if needed.
+		 * As threads are considered to be trust-realm
+		 * equivalent (see ptrace_may_access), it is safe to
+		 * allow one thread to transition the other.
+		 */
+		if (thread->seccomp.mode == SECCOMP_MODE_DISABLED) {
+			/*
+			 * Don't let an unprivileged task work around
+			 * the no_new_privs restriction by creating
+			 * a thread that sets it up, enters seccomp,
+			 * then dies.
+			 */
+			if (task_no_new_privs(caller))
+				task_set_no_new_privs(thread);
+
+			seccomp_assign_mode(thread, SECCOMP_MODE_FILTER);
+		}
+	}
+}
+
 /**
  * seccomp_prepare_filter: Prepares a seccomp filter for use.
  * @fprog: BPF program to install
@@ -364,6 +473,15 @@ static long seccomp_attach_filter(unsigned int flags,
 	if (total_insns > MAX_INSNS_PER_PATH)
 		return -ENOMEM;
 
+	/* If thread sync has been requested, check that it is possible. */
+	if (flags & SECCOMP_FILTER_FLAG_TSYNC) {
+		int ret;
+
+		ret = seccomp_can_sync_threads();
+		if (ret)
+			return ret;
+	}
+
 	/*
 	 * If there is an existing filter, make it the prev and don't drop its
 	 * task reference.
@@ -371,6 +489,10 @@ static long seccomp_attach_filter(unsigned int flags,
 	filter->prev = current->seccomp.filter;
 	current->seccomp.filter = filter;
 
+	/* Now that the new filter is in place, synchronize to all threads. */
+	if (flags & SECCOMP_FILTER_FLAG_TSYNC)
+		seccomp_sync_threads();
+
 	return 0;
 }
 
@@ -590,7 +712,7 @@ static long seccomp_set_mode_filter(unsigned int flags,
 	long ret = -EINVAL;
 
 	/* Validate flags. */
-	if (flags != 0)
+	if (flags & ~SECCOMP_FILTER_FLAG_MASK)
 		return -EINVAL;
 
 	/* Prepare the new filter before holding any locks. */
@@ -598,6 +720,14 @@ static long seccomp_set_mode_filter(unsigned int flags,
 	if (IS_ERR(prepared))
 		return PTR_ERR(prepared);
 
+	/*
+	 * Make sure we cannot change seccomp or nnp state via TSYNC
+	 * while another thread is in the middle of calling exec.
+	 */
+	if (flags & SECCOMP_FILTER_FLAG_TSYNC &&
+	    mutex_lock_killable(&current->signal->cred_guard_mutex))
+		goto out_free;
+
 	spin_lock_irq(&current->sighand->siglock);
 
 	if (!seccomp_may_assign_mode(seccomp_mode))
@@ -612,6 +742,9 @@ static long seccomp_set_mode_filter(unsigned int flags,
 	seccomp_assign_mode(current, seccomp_mode);
 out:
 	spin_unlock_irq(&current->sighand->siglock);
+	if (flags & SECCOMP_FILTER_FLAG_TSYNC)
+		mutex_unlock(&current->signal->cred_guard_mutex);
+out_free:
 	seccomp_filter_free(prepared);
 	return ret;
 }
-- 
1.7.9.5

^ permalink raw reply related

* [PATCH v4 2/3] soc: qcom-rpm: Driver for the Qualcomm RPM
From: Bjorn Andersson @ 2014-07-17 18:17 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <CAMf-jSnPpo39cXLbM9cefUr37J=hnEHqfJSetORxMnjsdoANdw@mail.gmail.com>

On Thu, Jul 17, 2014 at 10:33 AM, pramod gurav
<pramod.gurav.etc@gmail.com> wrote:
> Hi Bjorn,
>
> On Wed, Jul 16, 2014 at 4:30 AM, Bjorn Andersson
> <bjorn.andersson@sonymobile.com> wrote:
>> Driver for the Resource Power Manager (RPM) found in Qualcomm 8660, 8960
>> and 8064 based devices. The driver exposes resources that child drivers
>> can operate on; to implementing regulator, clock and bus frequency
>> drivers.
>>
>
> [snip]
>
>> +       }
>> +
>> +       ret = irq_set_irq_wake(irq_ack, 1);
>
> This calls fails and throws error on my ifc6410 with 3.16-rc5.
> Does this driver depend on pincntrl. Looks like the DT support for
> pincntrl driver is missing in apq8064 dts in mainline.
> Is that right?
>

This is a gic interrupt, to it's unrelated to pinctrl.

What happens is that you end up in gic_set_wake() checking for the
architecture specific implementation of irq_set_wake; on modern
Qualcomm platforms waking the system up from sleep seems to be handled
in it's entirety by the "Modem Power Manager" (or "MSM Power Manager")
- in short MPM.

So once we introduce a driver for the mpm hardware this should
register these functions with the gic and we should get those marked
as wakeup sources.

It can be argued that this should be an error instead of just a
"warning", but for systems where this fails; i.e. systems without the
mpm driver we will not be able to go to sleep anyways, so if this call
fails we shouldn't expect to ever be woken up again.


So please ignore this warning for now; we will get there at some point.

Regards,
Bjorn

^ permalink raw reply

* [GIT PULL] ARM: OMAP2+: clock cleanup for 3.17
From: Paul Walmsley @ 2014-07-17 18:18 UTC (permalink / raw)
  To: linux-arm-kernel

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Tony,

The following changes since commit a497c3ba1d97fc69c1e78e7b96435ba8c2cb42ee:

  Linux 3.16-rc2 (2014-06-21 19:02:54 -1000)

are available in the git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/pjw/omap-pending.git tags/for-v3.17/omap-clock-a

for you to fetch changes up to acd052bb8119dd9117e0af48ff0ac6e56e61b6b4:

  ARM: OMAP2+: clock/interface: remove some headers from clkt_iclk.c file (2014-07-15 14:09:24 -0600)

- ----------------------------------------------------------------
An OMAP clock cleanup series for 3.17 from Tero Kristo.
This is in preparation for moving this code into drivers/clk/ti.

Basic build, boot, and PM test logs are here:

http://www.pwsan.com/omap/testlogs/clock-a-v3.17/20140717034329/

- ----------------------------------------------------------------
Tero Kristo (12):
      ARM: OMAP4+: clock: remove DEFINE_CLK_OMAP_HSDIVIDER macro
      ARM: OMAP4+: dpll: remove cpu_is_omap44xx checks
      ARM: OMAP4+: dpll44xx: remove cm-regbits-44xx.h and clock44xx.h includes
      ARM: OMAP2+: clock: introduce ti_clk_features flags
      ARM: OMAP2+: clock: add fint values to the ti_clk_features struct
      ARM: OMAP2+: clock/dpll: add private API for checking if DPLL is in bypass
      ARM: OMAP2+: clock/dpll: convert bypass check to use clk_features
      ARM: OMAP2+: clock/dpll: add jitter correction behind clk_features
      ARM: OMAP2+: clock/interface: add a clk_features definition for idlest value
      ARM: OMAP2+: clock/dpll: remove unused header includes from clkt_dpll.c
      ARM: OMAP2+: clock/dpll: remove unused header includes from dpll3xxx.c
      ARM: OMAP2+: clock/interface: remove some headers from clkt_iclk.c file

 arch/arm/mach-omap2/clkt_dpll.c | 98 +++++++++++++++++++----------------------
 arch/arm/mach-omap2/clkt_iclk.c |  8 ++--
 arch/arm/mach-omap2/clock.c     | 76 +++++++++++++++++++++++++++++---
 arch/arm/mach-omap2/clock.h     | 44 ++++++++----------
 arch/arm/mach-omap2/dpll3xxx.c  |  7 +--
 arch/arm/mach-omap2/dpll44xx.c  | 19 +++++---
 arch/arm/mach-omap2/io.c        |  2 +
 7 files changed, 154 insertions(+), 100 deletions(-)
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1

iQIcBAEBAgAGBQJTyBMxAAoJEMePsQ0LvSpLiIAQAJl7wHt44ZVVOwdeabNQppl3
PjqUjgQaT60Kpz4utPg0lOd6pJzzmLQ2cHtbV39U4ZCyMnTFi7wArmOR5htir0gh
zBDSBEXNJh4ZFyBBNTdlxhJcVjRnO+ar3HuuqdtQLEP19795IxY8Rk9X3ric+35W
1oeFj9EcCs9Reet4l8FnY5GKcLJvL5KiKD8BIsBa8U1AlHu3Cw8RtfpdQX6zdNMO
1THNKFsIt5PdTmbTuWA9c161DciNeN0QuXQVdqWYzepyOP1rS3zwKyZDEC3mE5Bl
TEOuANaJy2IqLJVTSal0hounMPmOUOp/wXr06s2chZ/bGLKZ/riTZm746cSFUTuZ
d8sGMxVJ2X9D2MexBzlO/3blg6503WCT1slIZwhEoiqQoDiWG6E/29ZrP752rFWy
c/Sa5Ro10pL0wAmjaU4hNxIE7ekksJKXROiqcOiqNeI0egTRu5wn/dNArPAewj25
K6T5sVes9getq98LfgCFdI+9QVr75gGkP7tWjuLhMnp+DSSdVfWlp3XPdazDFPdO
qkN7LByiHPTQUsbM7yCUfnlRVPpZdPUDkTJ3VHmbw/VhzCs90kirMywtheXJMWwT
carzv/FzdkZAhy1gfnH+GR/JaTm2JizUvA3xEb+V43JvPvgIzxiNhCM4eTYi7ElE
svIdX6E2JPB4JeP3DTwo
=z9Eq
-----END PGP SIGNATURE-----

^ permalink raw reply

* OMAP baseline test results for v3.16-rc5
From: Paul Walmsley @ 2014-07-17 18:25 UTC (permalink / raw)
  To: linux-arm-kernel

Here are some basic OMAP test results for Linux v3.16-rc5.
Logs and other details at:

    http://www.pwsan.com/omap/testlogs/test_v3.16-rc5/20140716140950/


Test summary
------------

Build: zImage:
    Pass (16/16): multi_v7_defconfig, omap2plus_defconfig,
		  omap2plus_defconfig_am33xx_only,
		  omap2plus_defconfig_am43xx_only,
		  omap2plus_defconfig_2430sdp_only,
		  omap2plus_defconfig_cpupm, omap2plus_defconfig_no_pm,
		  omap2plus_defconfig_n800_only_a,
		  omap2plus_defconfig_n800_multi_omap2xxx,
		  omap2plus_defconfig_omap2_4_only,
		  omap2plus_defconfig_omap3_4_only,
		  omap2plus_defconfig_dra7xx_only,
		  rmk_omap3430_ldp_allnoconfig,
		  rmk_omap3430_ldp_oldconfig,
		  rmk_omap4430_sdp_allnoconfig,
		  rmk_omap4430_sdp_oldconfig

Build: uImage:
    Pass ( 3/ 3): omap1_defconfig, omap1_defconfig_1510innovator_only,
		  omap1_defconfig_5912osk_only

Build: uImage+dtb:
    Pass (10/10): omap2plus_defconfig_am33xx_only/am335x-bone,
		  omap2plus_defconfig/omap4-panda,
		  omap2plus_defconfig/omap4-panda-es,
		  omap2plus_defconfig/am3517-evm,
		  omap2plus_defconfig/omap2430-sdp,
		  omap2plus_defconfig/omap3-beagle,
		  omap2plus_defconfig/omap3-beagle-xm,
		  omap2plus_defconfig/omap3-evm-37xx,
		  omap2plus_defconfig/omap4-var-som,
		  omap2plus_defconfig/omap5-uevm

Boot to userspace:
    FAIL ( 1/14): 2430sdp
    skip ( 1/14): 5912osk
    Pass (12/14): 2420n800, 3517evm, 3530es3beagle, 3730beaglexm,
		  37xxevm, 4430es2panda, 4460pandaes, am335xbone,
		  am335xbonelt, cmt3517, 4460varsomom, 5430es2uevm

PM: chip retention via suspend:
    FAIL ( 3/ 7): 2430sdp, 4430es2panda, 4460varsomom
    Pass ( 4/ 7): 3530es3beagle, 3730beaglexm, 37xxevm, 4460pandaes

PM: chip retention via dynamic idle:
    FAIL ( 7/ 7): 2430sdp, 3530es3beagle, 3730beaglexm, 37xxevm,
		  4430es2panda, 4460pandaes, 4460varsomom

PM: chip off except CORE via suspend:
    Pass ( 1/ 1): 3730beaglexm

PM: chip off except CORE via dynamic idle:
    FAIL ( 1/ 1): 3730beaglexm

PM: chip off via suspend:
    FAIL ( 4/ 5): 3530es3beagle, 4430es2panda, 4460pandaes,
		  4460varsomom
    Pass ( 1/ 5): 37xxevm

PM: chip off via dynamic idle:
    FAIL ( 5/ 5): 3530es3beagle, 37xxevm, 4430es2panda, 4460pandaes,
		  4460varsomom


vmlinux object size
(delta in bytes from test_v3.16-rc4 (cd3de83f147601356395b57a8673e9c5ff1e59d1)):
   text     data      bss    total  kernel
   +160        0        0     +160  omap1_defconfig
   +192        0        0     +192  omap1_defconfig_1510innovator_only
   +160        0        0     +160  omap1_defconfig_5912osk_only
  +1044     -160        0     +884  multi_v7_defconfig
   +284      +16        0     +300  omap2plus_defconfig
   +284       +8        0     +292  omap2plus_defconfig_2430sdp_only
   +284       +8        0     +292  omap2plus_defconfig_am33xx_only
   +524     +192        0     +716  omap2plus_defconfig_am43xx_only
   +348      -16        0     +332  omap2plus_defconfig_cpupm
   +284       +8        0     +292  omap2plus_defconfig_dra7xx_only
   +172        0        0     +172  omap2plus_defconfig_n800_multi_omap2xxx
   +140       -8        0     +132  omap2plus_defconfig_n800_only_a
   +348      +16        0     +364  omap2plus_defconfig_no_pm
   +348      -16        0     +332  omap2plus_defconfig_omap2_4_only
   +348      +16        0     +364  omap2plus_defconfig_omap3_4_only
   +284      +16        0     +300  omap2plus_defconfig_omap5_only
   +216        0      -16     +200  rmk_omap3430_ldp_allnoconfig
   +160        0        0     +160  rmk_omap3430_ldp_oldconfig
   +184        0      -16     +168  rmk_omap4430_sdp_allnoconfig
   +168       -8        0     +160  rmk_omap4430_sdp_oldconfig

Boot-time memory difference
(delta in bytes from test_v3.16-rc4 (cd3de83f147601356395b57a8673e9c5ff1e59d1))
  avail  rsrvd   high  freed  board          kconfig
  (no differences)


Two bugs were fixed in the PM testing scripts for this test. One of them 
prevented UART wakeups from being enabled.  The other caused off-mode test 
results to not be reported if retention test results failed.  (Thanks to 
Tony for prompting me to closely review the test scripts here and fix 
them.)  After fixing these, suspend/resume power management, including 
both retention and off-idle, now appears to be working for OMAP37xx-series 
chips.  Dynamic PM idle entry still appears broken, as does PM for 
OMAP35xx-series chips and OMAP4 chips.

^ permalink raw reply

* [Patch v7 2/3] usb: phy: Add Qualcomm DWC3 HS/SS PHY drivers
From: Andy Gross @ 2014-07-17 18:28 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405593024.423213197@apps.rackspace.com>

On Thu, Jul 17, 2014 at 06:30:24AM -0400, kiran.padwal at smartplayin.com wrote:
> Hi,
> 
> On Mon, Jun 30, 2014 at 9:33 PM, Andy Gross <agross@codeaurora.org> wrote: 
> > From: "Ivan T. Ivanov" <iivanov@mm-sol.com>
> > 
> > These drivers handles control and configuration of the HS
> > and SS USB PHY transceivers. They are part of the driver
> .
> [snip]
> .

I'll remove all the unused/unnecessary defines in the next version.

Thanks!

-- 
sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation

^ permalink raw reply

* [PATCH] dmaengine: qcom_bam_dma: Add descriptor flags
From: Andy Gross @ 2014-07-17 18:31 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20140714163616.GA4387@intel.com>

On Mon, Jul 14, 2014 at 10:06:16PM +0530, Vinod Koul wrote:
> On Fri, May 30, 2014 at 03:49:50PM -0500, Andy Gross wrote:
> > This patch adds support for end of transaction (EOT) and notify when done (NWD)
> > hardware descriptor flags.
> > 
> > The EOT flag requests that the peripheral assert an end of transaction interrupt
> > when that descriptor is complete.  It also results in special signaling protocol
> > that is used between the attached peripheral and the core using the DMA
> > controller.  Clients will specify DMA_PREP_INTERRUPT to enable this flag.
> > 
> > The NWD flag requests that the peripheral wait until the data has been fully
> > processed by the peripheral before moving on to the next descriptor.  Clients
> > will specify DMA_PREP_FENCE to enable this flag.
> 
> I am going to apply this, but pls send a follow up patch to add comments on the
> flags and their behaviour. I think it is required!

Will do.

<snip>

> >  #define DESC_FLAG_EOT BIT(14)
> >  #define DESC_FLAG_EOB BIT(13)
> > +#define DESC_FLAG_NWD BIT(12)
> explaining behvaiour will help..

In the followup, I'll put in a lengthy description of the INT/EOT/NWD and how
they are used in the signaling to/from the attached peripheral.


Thanks!

-- 
sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation

^ permalink raw reply

* [PATCH v2] i2c: efm32: correct namespacing of location property
From: Uwe Kleine-König @ 2014-07-17 18:42 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20140717134000.GK2740@katana>

On Thu, Jul 17, 2014 at 03:40:00PM +0200, Wolfram Sang wrote:
> On Fri, Jul 11, 2014 at 10:50:14AM +0200, Uwe Kleine-K?nig wrote:
> > Olof Johansson pointed out that usually the company name is picked as
> > namespace prefix to specific properties. So expect "energymicro,location"
> > but fall back to the previously introduced name "efm32,location".
> > 
> > Cc: Olof Johansson <olof@lixom.net>
> > Signed-off-by: Uwe Kleine-K?nig <u.kleine-koenig@pengutronix.de>
> 
> Applied to for-next, thanks! If you think this is better suited in
> for-current, let me know.
I'm not in a hurry. If you take it it's fine for me.

(BTW, I'm not sure i fI understand your branch naming. for-next means
"targeting the next merge window" and for-current means "targeting the
next release"? So for-next currently means 3.17-rc1?)

Thanks
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-K?nig            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply

* [PATCH 1/4] Revert "spi: s3c64xx: Added provision for dedicated cs pin"
From: Mark Brown @ 2014-07-17 18:46 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405523950-2231-2-git-send-email-javier.martinez@collabora.co.uk>

On Wed, Jul 16, 2014 at 05:19:07PM +0200, Javier Martinez Canillas wrote:
> This reverts commit 3146beec21b64f4551fcf0ac148381d54dc41b1b.

For the benefit of those who haven't memorized the SHA1s of every commit
that's "spi: s3c64xx: Added provision for dedicated cs pin" - please
include the human readable format whenever you reference a SHA1.

I've applied this.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20140717/901bfe82/attachment.sig>

^ permalink raw reply

* [PATCH 2/4] spi: s3c64xx: use the generic SPI "cs-gpios" property
From: Mark Brown @ 2014-07-17 18:48 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405523950-2231-3-git-send-email-javier.martinez@collabora.co.uk>

On Wed, Jul 16, 2014 at 05:19:08PM +0200, Javier Martinez Canillas wrote:
> From: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> 
> The s3c64xx SPI driver uses a custom DT binding to specify
> the GPIO used to drive the chip select (CS) line instead of
> using the generic "cs-gpios" property already defined in:
> Documentation/devicetree/bindings/spi/spi-bus.txt.

Applied, thanks.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20140717/417cc2a7/attachment-0001.sig>

^ permalink raw reply

* [PATCH 3/4] spi: samsung: Update binding documentation
From: Mark Brown @ 2014-07-17 18:48 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405523950-2231-4-git-send-email-javier.martinez@collabora.co.uk>

On Wed, Jul 16, 2014 at 05:19:09PM +0200, Javier Martinez Canillas wrote:
> From: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> 
> Samsung SPI driver now uses the generic SPI "cs-gpios"
> binding so update the documentation accordingly.

Applied, thanks.  Please do try to use changelogs that are consistent
with the general style, or at least consistent within a patch series.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20140717/73ce064e/attachment.sig>

^ permalink raw reply

* [PATCH 4/4] ARM: DTS: fix the chip select gpios definition in the SPI nodes
From: Mark Brown @ 2014-07-17 18:49 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405523950-2231-5-git-send-email-javier.martinez@collabora.co.uk>

On Wed, Jul 16, 2014 at 05:19:10PM +0200, Javier Martinez Canillas wrote:
> From: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> 
> This patch replaces the "cs-gpio" from "controller-data" node
> as was specified in the old binding and uses the standard
> "cs-gpios" property expected by the SPI core as is defined now
> in the spi-s3c64xx driver DT binding.

I've applied this one too since everything here really ought to go in
together and we should probably try to get this into v3.16 - Kukjin,
please say if this is an issue and I can revert.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20140717/b479e79f/attachment.sig>

^ permalink raw reply

* [PATCH 7/8] mailbox: f_mhu: add driver for Fujitsu MHU controller
From: Sudeep Holla @ 2014-07-17 18:51 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <CAJe_ZhcROjhXebJetyJus43jNKoBU60jXXRLwnHwMdPJ7LPq7Q@mail.gmail.com>



On 17/07/14 18:07, Jassi Brar wrote:
> On 17 July 2014 20:39, Sudeep Holla <sudeep.holla@arm.com> wrote:
>>
>>
>> On 17/07/14 13:56, Jassi Brar wrote:
>>>
>>> On 17 July 2014 16:01, Sudeep Holla <sudeep.holla@arm.com> wrote:
>>>>

[...]

>>>>>> This note could be added as how this mailbox works in general and
>>>>>> it's not just Rx right ? Even Tx done is based on this logic.
>>>>>> Basically the logic is valid on both directions.
>>>>>>
>>>>> Yes that is a protocol level agreement. Different f/w may behave
>>>>> differently.
>>>>>
>>>>
>>>> Ok, I am not sure if that's entirely true because the MHU spec says it
>>>> drives
>>>> the signal using a 32-bit register, with all 32 bits logically ORed
>>>> together.
>>>> Usually only Rx signals are wired to interrupts and Tx needs to be polled
>>>> but that's entirely implementation choice I believe.
>>>>
>>> On my platform, _STAT register only carries the command code but
>>> actual data is exchanged via SharedMemory/SHM. Now we need to know
>>> when the sent data packet (in SHM) has been consumed by the remote -
>>> for which our protocol mandates the remote clear the TX_STAT register.
>>> And vice-versa for RX.
>>>
>>>    Some other f/w may choose some other mechanism for TX-Done - say some
>>> ACK bit set or even some time bound guarantee.
>>>
>>>> More over if it's protocol level agreement it should not belong here :)
>>>>
>>> My f/w expects the RX_STAT cleared after reading data from SHM. Where
>>> do you think is the right place to clear RX_STAT?
>>>
>>
>> I understand that and what I am saying is the MHU is designed like that
>> and protocol is just using it. There's nothing specific to protocol. Ideally
>> if an implementation has both Rx and Tx interrupts, the RX_CLEAR from here
>> raises an interrupt to the firmware. In absence of it we need polling that's
>> what both Linux and firmware does for Tx case.
>>
>> Even on Juno, it's same. But we should be able to extend it to support Tx
>> if an implementation supports. Similarly the firmware can make use of the
>> same when Linux clears Rx(it would be Tx complete/ack for the firmware)
>>
>> When we need to make this driver work across different firmware, you just
>> can't rely on the firmware protocol, hence I am asking to drop those
>> comments.
>>
> OK, I will remove the comment.
>
>>
>>> I have said many times in many threads... its the mailbox controller
>>> _and_ the remote f/w that should be seen as one entity. It may not be
>>> possible to write a controller driver that works with any remote
>>> firmware.
>>>
>>
>> It should be possible if the remote protocol just use the same hardware
>> feature without any extra software policy at the lowest level(raw Tx and
>> Rx).
>>
> I wouldn't count on f/w always done the right way. And I am speaking
> from my whatever first hand experience :D
> And sometimes there may just be bugs that need some quirks at controller level.
>

Agreed, and I too have similar experience. This is exact reason why I am
urging for threaded irq, unless we have real requirement for hard irq.

>> I believe that's what we need here if we want this driver to work
>> on both Juno and your platform. Agree ?
>>
> Does this driver not work for Juno?

I have not yet tried yet. For sure secure access will explode.

> If no, may I see your driver and the MHU manual (mine is 90% Japanese)?
>

It's quite similar to your one expect few comments which I have made.
Here is the public version of Juno spec[1]. Not sure if it covers MHU in
detail.

>>
>>>>
>>>>>>> +static int mhu_startup(struct mbox_chan *chan)
>>>>>>> +{
>>>>>>> +       struct mhu_link *mlink = (struct mhu_link *)chan->con_priv;
>>>>>>> +       unsigned long flags;
>>>>>>> +       u32 val;
>>>>>>> +       int ret;
>>>>>>> +
>>>>>>> +       pr_debug("%s:%d\n", __func__, __LINE__);
>>>>>>> +       spin_lock_irqsave(&mlink->lock, flags);
>>>>>>> +       val = readl_relaxed(mlink->tx_reg + INTR_STAT_OFS);
>>>>>>> +       writel_relaxed(val, mlink->tx_reg + INTR_CLR_OFS);
>>>>>>> +       spin_unlock_irqrestore(&mlink->lock, flags);
>>>>>>> +
>>>>>>> +       ret = request_irq(mlink->irq, mhu_rx_interrupt,
>>>>>>> +                         IRQF_SHARED, "mhu_link", chan);
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> Just a thought: Can this be threaded_irq instead ?
>>>>>> Can move request_irq to probe instead esp. if threaded_irq ?
>>>>>> That provides some flexibility to client's rx_callback.
>>>>>>
>>>>> This is controller driver, and can not know which client want
>>>>> rx_callback in hard-irq context and which in thread_fn context.
>>>>> Otherwise, request_irq() does evaluate to request_threaded_irq(), if
>>>>> thats what you mean.
>>>>
>>>>
>>>> Agreed but on contrary since MHU involves external firmware(running
>>>> on different processor) which might have it's own latency, I prefer
>>>> threaded over hard irq. And yes request_irq does evaluate to
>>>> request_threaded_irq but with thread_fn = NULL which is not what I want.
>>>>
>>> If remote has its own latency, that does not mean we add some more :)
>>>
>>
>> No what I meant is unless there is a real need to use hard irq, we
>> should prefer threaded one otherwise.
>>
> And how does controller discern a "real need" from a "soft need" to
> use hard irq?
> Even if the controller driver pushes data up from a threaded function,
> the client can't know the context and can't sleep because the
> intermediate API says the rx_callback should be assumed to be atomic.

Yes I am not arguing on that, it should assume atomic and not sleep.
I am saying we can avoid rx_callback in interrupt context if possible.
I will try to look at the protocol implementation tomorrow.

> Again, it maybe more efficient if I see your implementation of the
> driver and understand your concerns about mine.
>

As I said its almost same as this, except I call mbox_chan_received_data
in irq thread context. I prefer enabling other interrupts while copying
payload data.

>> Also by latency I meant what if
>> the remote firmware misbehaves. In threaded version you have little
>> more flexibility whereas hard irq is executed with interrupts disabled.
>> At least the system is responsive and only MHU interrupts are disabled.
>>
> If the remote firmware misbehaves, that is a bug of the platform. No
> mailbox API could/should account for such malfunctions.
>

No I didn't intend for any mailbox API to account it.

Regards,
Sudeep

[1] http://infocenter.arm.com/help/topic/com.arm.doc.dto0038a/index.html

^ permalink raw reply

* [PATCH] ARM: at91/dt: add missing clocks property to pwm node in sam9x5.dtsi
From: Boris BREZILLON @ 2014-07-17 19:03 UTC (permalink / raw)
  To: linux-arm-kernel

The pwm driver requires a clocks property referencing the pwm peripheral
clk.

Signed-off-by: Boris BREZILLON <boris.brezillon@free-electrons.com>
---
 arch/arm/boot/dts/at91sam9x5.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm/boot/dts/at91sam9x5.dtsi b/arch/arm/boot/dts/at91sam9x5.dtsi
index 2ebc421..6902f53 100644
--- a/arch/arm/boot/dts/at91sam9x5.dtsi
+++ b/arch/arm/boot/dts/at91sam9x5.dtsi
@@ -1124,6 +1124,7 @@
 				compatible = "atmel,at91sam9rl-pwm";
 				reg = <0xf8034000 0x300>;
 				interrupts = <18 IRQ_TYPE_LEVEL_HIGH 4>;
+				clocks = <&pwm_clk>;
 				#pwm-cells = <3>;
 				status = "disabled";
 			};
-- 
1.8.3.2

^ permalink raw reply related

* [PATCH 1/4] Revert "spi: s3c64xx: Added provision for dedicated cs pin"
From: Javier Martinez Canillas @ 2014-07-17 19:33 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20140717184656.GB17528@sirena.org.uk>

Hello Mark,

On 07/17/2014 08:46 PM, Mark Brown wrote:
> On Wed, Jul 16, 2014 at 05:19:07PM +0200, Javier Martinez Canillas wrote:
>> This reverts commit 3146beec21b64f4551fcf0ac148381d54dc41b1b.
> 
> For the benefit of those who haven't memorized the SHA1s of every commit
> that's "spi: s3c64xx: Added provision for dedicated cs pin" - please
> include the human readable format whenever you reference a SHA1.
> 

Ok, I'll take into account for the next time.

> I've applied this.
> 

Thanks a lot for your help and sorry for all the inconveniences that this series
caused to you.

Best regards,
Javier

^ permalink raw reply

* [PATCH v11 0/2] Add support for the Allwinner A31 DMA Controller
From: Maxime Ripard @ 2014-07-17 19:46 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

This patchset adds support for the DMA controller found in the
Allwinner A31 and A23 SoCs.

This has been tested using the newly introduced SPI driver on an A31
EVK. Support for DMA-driven SPI transfers will be the subject of
another patch serie.

This has been around for around 5 monthes now, and didn't get any
review but nitpicks for three versions, so I feel like it could be
merged quite quickly.

Thanks,
Maxime

Changes from v10:
  - Added in 

Changes from v9:
  - Rebased on top of 3.16-rc1
  - Fixed the whitespace error in the documentation

Changes from v8:
  - Drop the select on DMA_OF
  - Depend on COMPILE_TEST to get more build tests coverage

Changes from v7:
  - select DMA_OF, since we're only relying on DT
  - Properly kill the tasklet as suggested in
    https://lwn.net/Articles/588457/
  - Split up the dt bindings documentation into a separate patch

Changes from v6:
  - Dropped the merged patches and the clock patches that are pretty
    orthogonal to this driver

Changes from v5:
  - Rebased on top of 3.15-rc1

Changes from v4:
  - Removed the packed attribute on the LLI
  - Switched to using a NULL device pointer in clk_get on PLL6 and
    AHB1 mux to make explicit that we are getting global clocks
  - Switched from spin_lock_irqsave to spin_lock in the interrupt
    handler
  - Various nitpicks from Andy Shevchenko:
    + Switched to using %p printk formats for pointers
    + Inverted some tests to lose a level of indentation
    + Dropped ifdef DEBUG protecting calls to dev_dbg

Changes from v3:
  - A few other comments made by Andy Shevchenko were fixed:
    + Used references in %pa* printk formats
    + Used is_slave_direction in prep_slave_sg to make sure we were
      actually called for something, and to avoid making assumptions
      that we were actually called with the expected directions
    + A few others minor fixes: s/pr_err/dev_err/, etc.

Changes from v2:
  - Removed the clk_put calls in the clock protection functions
  - Splitted out the sunxi machines into several files
  - Moved the clock protection code into these new machine files
  - Moved the PLL6 reparenting to the DMA driver
  - Addressed various comments from Andy Shevchenko: switched to using
    devm_kcalloc, used correct printk formats for physical and DMA
    addresses, etc.

Changes from v1:
  - Removed the clk_put call in the clocks protecting patches
  - Minor fixes here and there as suggested by Andy Shevchenko: switch
    to dmam_pool_create, switch to dev_dbg instead of pr_debug, etc.

Maxime Ripard (2):
  Documentation: dt: Add Allwinner A31 DMA controller bindings
  dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller

 .../devicetree/bindings/dma/sun6i-dma.txt          |   45 +
 drivers/dma/Kconfig                                |    8 +
 drivers/dma/Makefile                               |    1 +
 drivers/dma/sun6i-dma.c                            | 1059 ++++++++++++++++++++
 4 files changed, 1113 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/dma/sun6i-dma.txt
 create mode 100644 drivers/dma/sun6i-dma.c

-- 
2.0.1

^ permalink raw reply

* [PATCH v11 1/2] Documentation: dt: Add Allwinner A31 DMA controller bindings
From: Maxime Ripard @ 2014-07-17 19:46 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405626376-471-1-git-send-email-maxime.ripard@free-electrons.com>

The Allwinner A31 DMA controller is rather simple to describe in the DT. Add
the bindings documentation.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 .../devicetree/bindings/dma/sun6i-dma.txt          | 45 ++++++++++++++++++++++
 1 file changed, 45 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/dma/sun6i-dma.txt

diff --git a/Documentation/devicetree/bindings/dma/sun6i-dma.txt b/Documentation/devicetree/bindings/dma/sun6i-dma.txt
new file mode 100644
index 000000000000..3e145c1675b1
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/sun6i-dma.txt
@@ -0,0 +1,45 @@
+Allwinner A31 DMA Controller
+
+This driver follows the generic DMA bindings defined in dma.txt.
+
+Required properties:
+
+- compatible:	Must be "allwinner,sun6i-a31-dma"
+- reg:		Should contain the registers base address and length
+- interrupts:	Should contain a reference to the interrupt used by this device
+- clocks:	Should contain a reference to the parent AHB clock
+- resets:	Should contain a reference to the reset controller asserting
+		this device in reset
+- #dma-cells :	Should be 1, a single cell holding a line request number
+
+Example:
+	dma: dma-controller at 01c02000 {
+		compatible = "allwinner,sun6i-a31-dma";
+		reg = <0x01c02000 0x1000>;
+		interrupts = <0 50 4>;
+		clocks = <&ahb1_gates 6>;
+		resets = <&ahb1_rst 6>;
+		#dma-cells = <1>;
+	};
+
+Clients:
+
+DMA clients connected to the A31 DMA controller must use the format
+described in the dma.txt file, using a two-cell specifier for each
+channel: a phandle plus one integer cells.
+The two cells in order are:
+
+1. A phandle pointing to the DMA controller.
+2. The port ID as specified in the datasheet
+
+Example:
+spi2: spi at 01c6a000 {
+	compatible = "allwinner,sun6i-a31-spi";
+	reg = <0x01c6a000 0x1000>;
+	interrupts = <0 67 4>;
+	clocks = <&ahb1_gates 22>, <&spi2_clk>;
+	clock-names = "ahb", "mod";
+	dmas = <&dma 25>, <&dma 25>;
+	dma-names = "rx", "tx";
+	resets = <&ahb1_rst 22>;
+};
-- 
2.0.1

^ permalink raw reply related

* [PATCH v11 2/2] dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller
From: Maxime Ripard @ 2014-07-17 19:46 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405626376-471-1-git-send-email-maxime.ripard@free-electrons.com>

The Allwinner A31 has a 16 channels DMA controller that it shares with the
newer A23. Although sharing some similarities with the DMA controller of the
older Allwinner SoCs, it's significantly different, I don't expect it to be
possible to share the driver for these two.

The A31 Controller is able to memory-to-memory or memory-to-device transfers on
the 16 channels in parallel.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
---
 drivers/dma/Kconfig     |    8 +
 drivers/dma/Makefile    |    1 +
 drivers/dma/sun6i-dma.c | 1059 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1068 insertions(+)
 create mode 100644 drivers/dma/sun6i-dma.c

diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 1eca7b9760e6..4b439270fb11 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -375,6 +375,14 @@ config XILINX_VDMA
 	  channels, Memory Mapped to Stream (MM2S) and Stream to
 	  Memory Mapped (S2MM) for the data transfers.
 
+config DMA_SUN6I
+	tristate "Allwinner A31 SoCs DMA support"
+	depends on MACH_SUN6I || COMPILE_TEST
+	select DMA_ENGINE
+	select DMA_VIRTUAL_CHANNELS
+	help
+	  Support for the DMA engine for Allwinner A31 SoCs.
+
 config DMA_ENGINE
 	bool
 
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index c779e1eb2db2..6807c50214c6 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_MOXART_DMA) += moxart-dma.o
 obj-$(CONFIG_FSL_EDMA) += fsl-edma.o
 obj-$(CONFIG_QCOM_BAM_DMA) += qcom_bam_dma.o
 obj-y += xilinx/
+obj-$(CONFIG_DMA_SUN6I) += sun6i-dma.o
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
new file mode 100644
index 000000000000..ce8d5d1b0ff4
--- /dev/null
+++ b/drivers/dma/sun6i-dma.c
@@ -0,0 +1,1059 @@
+/*
+ * Copyright (C) 2013-2014 Allwinner Tech Co., Ltd
+ * Author: Sugar <shuge@allwinnertech.com>
+ *
+ * Copyright (C) 2014 Maxime Ripard
+ * Maxime Ripard <maxime.ripard@free-electrons.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.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/dmapool.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_dma.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "virt-dma.h"
+
+/*
+ * There's 16 physical channels that can work in parallel.
+ *
+ * However we have 30 different endpoints for our requests.
+ *
+ * Since the channels are able to handle only an unidirectional
+ * transfer, we need to allocate more virtual channels so that
+ * everyone can grab one channel.
+ *
+ * Some devices can't work in both direction (mostly because it
+ * wouldn't make sense), so we have a bit fewer virtual channels than
+ * 2 channels per endpoints.
+ */
+
+#define NR_MAX_CHANNELS		16
+#define NR_MAX_REQUESTS		30
+#define NR_MAX_VCHANS		53
+
+/*
+ * Common registers
+ */
+#define DMA_IRQ_EN(x)		((x) * 0x04)
+#define DMA_IRQ_HALF			BIT(0)
+#define DMA_IRQ_PKG			BIT(1)
+#define DMA_IRQ_QUEUE			BIT(2)
+
+#define DMA_IRQ_CHAN_NR			8
+#define DMA_IRQ_CHAN_WIDTH		4
+
+
+#define DMA_IRQ_STAT(x)		((x) * 0x04 + 0x10)
+
+#define DMA_STAT		0x30
+
+/*
+ * Channels specific registers
+ */
+#define DMA_CHAN_ENABLE		0x00
+#define DMA_CHAN_ENABLE_START		BIT(0)
+#define DMA_CHAN_ENABLE_STOP		0
+
+#define DMA_CHAN_PAUSE		0x04
+#define DMA_CHAN_PAUSE_PAUSE		BIT(1)
+#define DMA_CHAN_PAUSE_RESUME		0
+
+#define DMA_CHAN_LLI_ADDR	0x08
+
+#define DMA_CHAN_CUR_CFG	0x0c
+#define DMA_CHAN_CFG_SRC_DRQ(x)		((x) & 0x1f)
+#define DMA_CHAN_CFG_SRC_IO_MODE	BIT(5)
+#define DMA_CHAN_CFG_SRC_LINEAR_MODE	(0 << 5)
+#define DMA_CHAN_CFG_SRC_BURST(x)	(((x) & 0x3) << 7)
+#define DMA_CHAN_CFG_SRC_WIDTH(x)	(((x) & 0x3) << 9)
+
+#define DMA_CHAN_CFG_DST_DRQ(x)		(DMA_CHAN_CFG_SRC_DRQ(x) << 16)
+#define DMA_CHAN_CFG_DST_IO_MODE	(DMA_CHAN_CFG_SRC_IO_MODE << 16)
+#define DMA_CHAN_CFG_DST_LINEAR_MODE	(DMA_CHAN_CFG_SRC_LINEAR_MODE << 16)
+#define DMA_CHAN_CFG_DST_BURST(x)	(DMA_CHAN_CFG_SRC_BURST(x) << 16)
+#define DMA_CHAN_CFG_DST_WIDTH(x)	(DMA_CHAN_CFG_SRC_WIDTH(x) << 16)
+
+#define DMA_CHAN_CUR_SRC	0x10
+
+#define DMA_CHAN_CUR_DST	0x14
+
+#define DMA_CHAN_CUR_CNT	0x18
+
+#define DMA_CHAN_CUR_PARA	0x1c
+
+
+/*
+ * Various hardware related defines
+ */
+#define LLI_LAST_ITEM	0xfffff800
+#define NORMAL_WAIT	8
+#define DRQ_SDRAM	1
+
+/*
+ * Hardware representation of the LLI
+ *
+ * The hardware will be fed the physical address of this structure,
+ * and read its content in order to start the transfer.
+ */
+struct sun6i_dma_lli {
+	u32			cfg;
+	u32			src;
+	u32			dst;
+	u32			len;
+	u32			para;
+	u32			p_lli_next;
+
+	/*
+	 * This field is not used by the DMA controller, but will be
+	 * used by the CPU to go through the list (mostly for dumping
+	 * or freeing it).
+	 */
+	struct sun6i_dma_lli	*v_lli_next;
+};
+
+
+struct sun6i_desc {
+	struct virt_dma_desc	vd;
+	dma_addr_t		p_lli;
+	struct sun6i_dma_lli	*v_lli;
+};
+
+struct sun6i_pchan {
+	u32			idx;
+	void __iomem		*base;
+	struct sun6i_vchan	*vchan;
+	struct sun6i_desc	*desc;
+	struct sun6i_desc	*done;
+};
+
+struct sun6i_vchan {
+	struct virt_dma_chan	vc;
+	struct list_head	node;
+	struct dma_slave_config	cfg;
+	struct sun6i_pchan	*phy;
+	u8			port;
+};
+
+struct sun6i_dma_dev {
+	struct dma_device	slave;
+	void __iomem		*base;
+	struct clk		*clk;
+	int			irq;
+	spinlock_t		lock;
+	struct reset_control	*rstc;
+	struct tasklet_struct	task;
+	atomic_t		tasklet_shutdown;
+	struct list_head	pending;
+	struct dma_pool		*pool;
+	struct sun6i_pchan	*pchans;
+	struct sun6i_vchan	*vchans;
+};
+
+static struct device *chan2dev(struct dma_chan *chan)
+{
+	return &chan->dev->device;
+}
+
+static inline struct sun6i_dma_dev *to_sun6i_dma_dev(struct dma_device *d)
+{
+	return container_of(d, struct sun6i_dma_dev, slave);
+}
+
+static inline struct sun6i_vchan *to_sun6i_vchan(struct dma_chan *chan)
+{
+	return container_of(chan, struct sun6i_vchan, vc.chan);
+}
+
+static inline struct sun6i_desc *
+to_sun6i_desc(struct dma_async_tx_descriptor *tx)
+{
+	return container_of(tx, struct sun6i_desc, vd.tx);
+}
+
+static inline void sun6i_dma_dump_com_regs(struct sun6i_dma_dev *sdev)
+{
+	dev_dbg(sdev->slave.dev, "Common register:\n"
+		"\tmask0(%04x): 0x%08x\n"
+		"\tmask1(%04x): 0x%08x\n"
+		"\tpend0(%04x): 0x%08x\n"
+		"\tpend1(%04x): 0x%08x\n"
+		"\tstats(%04x): 0x%08x\n",
+		DMA_IRQ_EN(0), readl(sdev->base + DMA_IRQ_EN(0)),
+		DMA_IRQ_EN(1), readl(sdev->base + DMA_IRQ_EN(1)),
+		DMA_IRQ_STAT(0), readl(sdev->base + DMA_IRQ_STAT(0)),
+		DMA_IRQ_STAT(1), readl(sdev->base + DMA_IRQ_STAT(1)),
+		DMA_STAT, readl(sdev->base + DMA_STAT));
+}
+
+static inline void sun6i_dma_dump_chan_regs(struct sun6i_dma_dev *sdev,
+					    struct sun6i_pchan *pchan)
+{
+	phys_addr_t reg = __virt_to_phys((unsigned long)pchan->base);
+
+	dev_dbg(sdev->slave.dev, "Chan %d reg: %pa\n"
+		"\t___en(%04x): \t0x%08x\n"
+		"\tpause(%04x): \t0x%08x\n"
+		"\tstart(%04x): \t0x%08x\n"
+		"\t__cfg(%04x): \t0x%08x\n"
+		"\t__src(%04x): \t0x%08x\n"
+		"\t__dst(%04x): \t0x%08x\n"
+		"\tcount(%04x): \t0x%08x\n"
+		"\t_para(%04x): \t0x%08x\n\n",
+		pchan->idx, &reg,
+		DMA_CHAN_ENABLE,
+		readl(pchan->base + DMA_CHAN_ENABLE),
+		DMA_CHAN_PAUSE,
+		readl(pchan->base + DMA_CHAN_PAUSE),
+		DMA_CHAN_LLI_ADDR,
+		readl(pchan->base + DMA_CHAN_LLI_ADDR),
+		DMA_CHAN_CUR_CFG,
+		readl(pchan->base + DMA_CHAN_CUR_CFG),
+		DMA_CHAN_CUR_SRC,
+		readl(pchan->base + DMA_CHAN_CUR_SRC),
+		DMA_CHAN_CUR_DST,
+		readl(pchan->base + DMA_CHAN_CUR_DST),
+		DMA_CHAN_CUR_CNT,
+		readl(pchan->base + DMA_CHAN_CUR_CNT),
+		DMA_CHAN_CUR_PARA,
+		readl(pchan->base + DMA_CHAN_CUR_PARA));
+}
+
+static inline int convert_burst(u32 maxburst, u8 *burst)
+{
+	switch (maxburst) {
+	case 1:
+		*burst = 0;
+		break;
+	case 8:
+		*burst = 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static inline int convert_buswidth(enum dma_slave_buswidth addr_width, u8 *width)
+{
+	switch (addr_width) {
+	case DMA_SLAVE_BUSWIDTH_1_BYTE:
+		*width = 0;
+		break;
+	case DMA_SLAVE_BUSWIDTH_2_BYTES:
+		*width = 1;
+		break;
+	case DMA_SLAVE_BUSWIDTH_4_BYTES:
+		*width = 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void *sun6i_dma_lli_add(struct sun6i_dma_lli *prev,
+			       struct sun6i_dma_lli *next,
+			       dma_addr_t next_phy,
+			       struct sun6i_desc *txd)
+{
+	if ((!prev && !txd) || !next)
+		return NULL;
+
+	if (!prev) {
+		txd->p_lli = next_phy;
+		txd->v_lli = next;
+	} else {
+		prev->p_lli_next = next_phy;
+		prev->v_lli_next = next;
+	}
+
+	next->p_lli_next = LLI_LAST_ITEM;
+	next->v_lli_next = NULL;
+
+	return next;
+}
+
+static inline int sun6i_dma_cfg_lli(struct sun6i_dma_lli *lli,
+				    dma_addr_t src,
+				    dma_addr_t dst, u32 len,
+				    struct dma_slave_config *config)
+{
+	u8 src_width, dst_width, src_burst, dst_burst;
+	int ret;
+
+	if (!config)
+		return -EINVAL;
+
+	ret = convert_burst(config->src_maxburst, &src_burst);
+	if (ret)
+		return ret;
+
+	ret = convert_burst(config->dst_maxburst, &dst_burst);
+	if (ret)
+		return ret;
+
+	ret = convert_buswidth(config->src_addr_width, &src_width);
+	if (ret)
+		return ret;
+
+	ret = convert_buswidth(config->dst_addr_width, &dst_width);
+	if (ret)
+		return ret;
+
+	lli->cfg = DMA_CHAN_CFG_SRC_BURST(src_burst) |
+		DMA_CHAN_CFG_SRC_WIDTH(src_width) |
+		DMA_CHAN_CFG_DST_BURST(dst_burst) |
+		DMA_CHAN_CFG_DST_WIDTH(dst_width);
+
+	lli->src = src;
+	lli->dst = dst;
+	lli->len = len;
+	lli->para = NORMAL_WAIT;
+
+	return 0;
+}
+
+static inline void sun6i_dma_dump_lli(struct sun6i_vchan *vchan,
+				      struct sun6i_dma_lli *lli)
+{
+	phys_addr_t p_lli = __virt_to_phys((unsigned long)lli);
+
+	dev_dbg(chan2dev(&vchan->vc.chan),
+		"\n\tdesc:   p - %pa v - 0x%p\n"
+		"\t\tc - 0x%08x s - 0x%08x d - 0x%08x\n"
+		"\t\tl - 0x%08x p - 0x%08x n - 0x%08x\n",
+		&p_lli, lli,
+		lli->cfg, lli->src, lli->dst,
+		lli->len, lli->para, lli->p_lli_next);
+}
+
+static void sun6i_dma_free_desc(struct virt_dma_desc *vd)
+{
+	struct sun6i_desc *txd = to_sun6i_desc(&vd->tx);
+	struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(vd->tx.chan->device);
+	struct sun6i_dma_lli *v_lli, *v_next;
+	dma_addr_t p_lli, p_next;
+
+	if (unlikely(!txd))
+		return;
+
+	p_lli = txd->p_lli;
+	v_lli = txd->v_lli;
+
+	while (v_lli) {
+		v_next = v_lli->v_lli_next;
+		p_next = v_lli->p_lli_next;
+
+		dma_pool_free(sdev->pool, v_lli, p_lli);
+
+		v_lli = v_next;
+		p_lli = p_next;
+	}
+
+	kfree(txd);
+}
+
+static int sun6i_dma_terminate_all(struct sun6i_vchan *vchan)
+{
+	struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(vchan->vc.chan.device);
+	struct sun6i_pchan *pchan = vchan->phy;
+	unsigned long flags;
+	LIST_HEAD(head);
+
+	spin_lock(&sdev->lock);
+	list_del_init(&vchan->node);
+	spin_unlock(&sdev->lock);
+
+	spin_lock_irqsave(&vchan->vc.lock, flags);
+
+	vchan_get_all_descriptors(&vchan->vc, &head);
+
+	if (pchan) {
+		writel(DMA_CHAN_ENABLE_STOP, pchan->base + DMA_CHAN_ENABLE);
+		writel(DMA_CHAN_PAUSE_RESUME, pchan->base + DMA_CHAN_PAUSE);
+
+		vchan->phy = NULL;
+		pchan->vchan = NULL;
+		pchan->desc = NULL;
+		pchan->done = NULL;
+	}
+
+	spin_unlock_irqrestore(&vchan->vc.lock, flags);
+
+	vchan_dma_desc_free_list(&vchan->vc, &head);
+
+	return 0;
+}
+
+static int sun6i_dma_start_desc(struct sun6i_vchan *vchan)
+{
+	struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(vchan->vc.chan.device);
+	struct virt_dma_desc *desc = vchan_next_desc(&vchan->vc);
+	struct sun6i_pchan *pchan = vchan->phy;
+	u32 irq_val, irq_reg, irq_offset;
+
+	if (!pchan)
+		return -EAGAIN;
+
+	if (!desc) {
+		pchan->desc = NULL;
+		pchan->done = NULL;
+		return -EAGAIN;
+	}
+
+	list_del(&desc->node);
+
+	pchan->desc = to_sun6i_desc(&desc->tx);
+	pchan->done = NULL;
+
+	sun6i_dma_dump_lli(vchan, pchan->desc->v_lli);
+
+	irq_reg = pchan->idx / DMA_IRQ_CHAN_NR;
+	irq_offset = pchan->idx % DMA_IRQ_CHAN_NR;
+
+	irq_val = readl(sdev->base + DMA_IRQ_EN(irq_offset));
+	irq_val |= DMA_IRQ_QUEUE << (irq_offset * DMA_IRQ_CHAN_WIDTH);
+	writel(irq_val, sdev->base + DMA_IRQ_EN(irq_offset));
+
+	writel(pchan->desc->p_lli, pchan->base + DMA_CHAN_LLI_ADDR);
+	writel(DMA_CHAN_ENABLE_START, pchan->base + DMA_CHAN_ENABLE);
+
+	sun6i_dma_dump_com_regs(sdev);
+	sun6i_dma_dump_chan_regs(sdev, pchan);
+
+	return 0;
+}
+
+static void sun6i_dma_tasklet(unsigned long data)
+{
+	struct sun6i_dma_dev *sdev = (struct sun6i_dma_dev *)data;
+	struct sun6i_vchan *vchan;
+	struct sun6i_pchan *pchan;
+	unsigned int pchan_alloc = 0;
+	unsigned int pchan_idx;
+
+	list_for_each_entry(vchan, &sdev->slave.channels, vc.chan.device_node) {
+		spin_lock_irq(&vchan->vc.lock);
+
+		pchan = vchan->phy;
+
+		if (pchan && pchan->done) {
+			if (sun6i_dma_start_desc(vchan)) {
+				/*
+				 * No current txd associated with this channel
+				 */
+				dev_dbg(sdev->slave.dev, "pchan %u: free\n",
+					pchan->idx);
+
+				/* Mark this channel free */
+				vchan->phy = NULL;
+				pchan->vchan = NULL;
+			}
+		}
+		spin_unlock_irq(&vchan->vc.lock);
+	}
+
+	spin_lock_irq(&sdev->lock);
+	for (pchan_idx = 0; pchan_idx < NR_MAX_CHANNELS; pchan_idx++) {
+		pchan = &sdev->pchans[pchan_idx];
+
+		if (pchan->vchan || list_empty(&sdev->pending))
+			continue;
+
+		vchan = list_first_entry(&sdev->pending,
+					 struct sun6i_vchan, node);
+
+		/* Remove from pending channels */
+		list_del_init(&vchan->node);
+		pchan_alloc |= BIT(pchan_idx);
+
+		/* Mark this channel allocated */
+		pchan->vchan = vchan;
+		vchan->phy = pchan;
+		dev_dbg(sdev->slave.dev, "pchan %u: alloc vchan %p\n",
+			pchan->idx, &vchan->vc);
+	}
+	spin_unlock_irq(&sdev->lock);
+
+	for (pchan_idx = 0; pchan_idx < NR_MAX_CHANNELS; pchan_idx++) {
+		if (!(pchan_alloc & BIT(pchan_idx)))
+			continue;
+
+		pchan = sdev->pchans + pchan_idx;
+		vchan = pchan->vchan;
+		if (vchan) {
+			spin_lock_irq(&vchan->vc.lock);
+			sun6i_dma_start_desc(vchan);
+			spin_unlock_irq(&vchan->vc.lock);
+		}
+	}
+}
+
+static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
+{
+	struct sun6i_dma_dev *sdev = dev_id;
+	struct sun6i_vchan *vchan;
+	struct sun6i_pchan *pchan;
+	int i, j, ret = IRQ_NONE;
+	u32 status;
+
+	for (i = 0; i < 2; i++) {
+		status = readl(sdev->base + DMA_IRQ_STAT(i));
+		if (!status)
+			continue;
+
+		dev_dbg(sdev->slave.dev, "DMA irq status %s: 0x%x\n",
+			i ? "high" : "low", status);
+
+		writel(status, sdev->base + DMA_IRQ_STAT(i));
+
+		for (j = 0; (j < 8) && status; j++) {
+			if (status & DMA_IRQ_QUEUE) {
+				pchan = sdev->pchans + j;
+				vchan = pchan->vchan;
+
+				if (vchan) {
+					spin_lock(&vchan->vc.lock);
+					vchan_cookie_complete(&pchan->desc->vd);
+					pchan->done = pchan->desc;
+					spin_unlock(&vchan->vc.lock);
+				}
+			}
+
+			status = status >> 4;
+		}
+
+		if (!atomic_read(&sdev->tasklet_shutdown))
+			tasklet_schedule(&sdev->task);
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_memcpy(
+		struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
+		size_t len, unsigned long flags)
+{
+	struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
+	struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
+	struct dma_slave_config *sconfig = &vchan->cfg;
+	struct sun6i_dma_lli *v_lli;
+	struct sun6i_desc *txd;
+	dma_addr_t p_lli;
+	int ret;
+
+	dev_dbg(chan2dev(chan),
+		"%s; chan: %d, dest: %pad, src: %pad, len: %zu. flags: 0x%08lx\n",
+		__func__, vchan->vc.chan.chan_id, &dest, &src, len, flags);
+
+	if (!len)
+		return NULL;
+
+	txd = kzalloc(sizeof(*txd), GFP_NOWAIT);
+	if (!txd)
+		return NULL;
+
+	v_lli = dma_pool_alloc(sdev->pool, GFP_NOWAIT, &p_lli);
+	if (!v_lli) {
+		dev_err(sdev->slave.dev, "Failed to alloc lli memory\n");
+		kfree(txd);
+		return NULL;
+	}
+
+	ret = sun6i_dma_cfg_lli(v_lli, src, dest, len, sconfig);
+	if (ret)
+		goto err_dma_free;
+
+	v_lli->cfg |= DMA_CHAN_CFG_SRC_DRQ(DRQ_SDRAM) |
+		DMA_CHAN_CFG_DST_DRQ(DRQ_SDRAM) |
+		DMA_CHAN_CFG_DST_LINEAR_MODE |
+		DMA_CHAN_CFG_SRC_LINEAR_MODE;
+
+	sun6i_dma_lli_add(NULL, v_lli, p_lli, txd);
+
+	sun6i_dma_dump_lli(vchan, v_lli);
+
+	return vchan_tx_prep(&vchan->vc, &txd->vd, flags);
+
+err_dma_free:
+	dma_pool_free(sdev->pool, v_lli, p_lli);
+	return NULL;
+}
+
+static struct dma_async_tx_descriptor *sun6i_dma_prep_slave_sg(
+		struct dma_chan *chan, struct scatterlist *sgl,
+		unsigned int sg_len, enum dma_transfer_direction dir,
+		unsigned long flags, void *context)
+{
+	struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
+	struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
+	struct dma_slave_config *sconfig = &vchan->cfg;
+	struct sun6i_dma_lli *v_lli, *prev = NULL;
+	struct sun6i_desc *txd;
+	struct scatterlist *sg;
+	dma_addr_t p_lli;
+	int i, ret;
+
+	if (!sgl)
+		return NULL;
+
+	if (!is_slave_direction(dir)) {
+		dev_err(chan2dev(chan), "Invalid DMA direction\n");
+		return NULL;
+	}
+
+	txd = kzalloc(sizeof(*txd), GFP_NOWAIT);
+	if (!txd)
+		return NULL;
+
+	for_each_sg(sgl, sg, sg_len, i) {
+		v_lli = dma_pool_alloc(sdev->pool, GFP_NOWAIT, &p_lli);
+		if (!v_lli) {
+			kfree(txd);
+			return NULL;
+		}
+
+		if (dir == DMA_MEM_TO_DEV) {
+			ret = sun6i_dma_cfg_lli(v_lli, sg_dma_address(sg),
+						sconfig->dst_addr, sg_dma_len(sg),
+						sconfig);
+			if (ret)
+				goto err_dma_free;
+
+			v_lli->cfg |= DMA_CHAN_CFG_DST_IO_MODE |
+				DMA_CHAN_CFG_SRC_LINEAR_MODE |
+				DMA_CHAN_CFG_SRC_DRQ(DRQ_SDRAM) |
+				DMA_CHAN_CFG_DST_DRQ(vchan->port);
+
+			dev_dbg(chan2dev(chan),
+				"%s; chan: %d, dest: %pad, src: %pad, len: %zu. flags: 0x%08lx\n",
+				__func__, vchan->vc.chan.chan_id,
+				&sconfig->dst_addr, &sg_dma_address(sg),
+				sg_dma_len(sg), flags);
+
+		} else {
+			ret = sun6i_dma_cfg_lli(v_lli, sconfig->src_addr,
+						sg_dma_address(sg), sg_dma_len(sg),
+						sconfig);
+			if (ret)
+				goto err_dma_free;
+
+			v_lli->cfg |= DMA_CHAN_CFG_DST_LINEAR_MODE |
+				DMA_CHAN_CFG_SRC_IO_MODE |
+				DMA_CHAN_CFG_DST_DRQ(DRQ_SDRAM) |
+				DMA_CHAN_CFG_SRC_DRQ(vchan->port);
+
+			dev_dbg(chan2dev(chan),
+				"%s; chan: %d, dest: %pad, src: %pad, len: %zu. flags: 0x%08lx\n",
+				__func__, vchan->vc.chan.chan_id,
+				&sg_dma_address(sg), &sconfig->src_addr,
+				sg_dma_len(sg), flags);
+		}
+
+		prev = sun6i_dma_lli_add(prev, v_lli, p_lli, txd);
+	}
+
+	dev_dbg(chan2dev(chan), "First: %pad\n", &txd->p_lli);
+	for (prev = txd->v_lli; prev; prev = prev->v_lli_next)
+		sun6i_dma_dump_lli(vchan, prev);
+
+	return vchan_tx_prep(&vchan->vc, &txd->vd, flags);
+
+err_dma_free:
+	dma_pool_free(sdev->pool, v_lli, p_lli);
+	return NULL;
+}
+
+static int sun6i_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
+		       unsigned long arg)
+{
+	struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
+	struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
+	struct sun6i_pchan *pchan = vchan->phy;
+	unsigned long flags;
+	int ret = 0;
+
+	switch (cmd) {
+	case DMA_RESUME:
+		dev_dbg(chan2dev(chan), "vchan %p: resume\n", &vchan->vc);
+
+		spin_lock_irqsave(&vchan->vc.lock, flags);
+
+		if (pchan) {
+			writel(DMA_CHAN_PAUSE_RESUME,
+			       pchan->base + DMA_CHAN_PAUSE);
+		} else if (!list_empty(&vchan->vc.desc_issued)) {
+			spin_lock(&sdev->lock);
+			list_add_tail(&vchan->node, &sdev->pending);
+			spin_unlock(&sdev->lock);
+		}
+
+		spin_unlock_irqrestore(&vchan->vc.lock, flags);
+		break;
+
+	case DMA_PAUSE:
+		dev_dbg(chan2dev(chan), "vchan %p: pause\n", &vchan->vc);
+
+		if (pchan) {
+			writel(DMA_CHAN_PAUSE_PAUSE,
+			       pchan->base + DMA_CHAN_PAUSE);
+		} else {
+			spin_lock(&sdev->lock);
+			list_del_init(&vchan->node);
+			spin_unlock(&sdev->lock);
+		}
+		break;
+
+	case DMA_TERMINATE_ALL:
+		ret = sun6i_dma_terminate_all(vchan);
+		break;
+	case DMA_SLAVE_CONFIG:
+		memcpy(&vchan->cfg, (void *)arg, sizeof(struct dma_slave_config));
+		break;
+	default:
+		ret = -ENXIO;
+		break;
+	}
+	return ret;
+}
+
+static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
+					   dma_cookie_t cookie,
+					   struct dma_tx_state *state)
+{
+	struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
+	struct sun6i_pchan *pchan = vchan->phy;
+	struct sun6i_dma_lli *lli;
+	struct virt_dma_desc *vd;
+	struct sun6i_desc *txd;
+	enum dma_status ret;
+	unsigned long flags;
+	size_t bytes = 0;
+
+	ret = dma_cookie_status(chan, cookie, state);
+	if (ret == DMA_COMPLETE)
+		return ret;
+
+	spin_lock_irqsave(&vchan->vc.lock, flags);
+
+	vd = vchan_find_desc(&vchan->vc, cookie);
+	txd = to_sun6i_desc(&vd->tx);
+
+	if (vd) {
+		for (lli = txd->v_lli; lli != NULL; lli = lli->v_lli_next)
+			bytes += lli->len;
+	} else if (!pchan || !pchan->desc) {
+		bytes = 0;
+	} else {
+		bytes = readl(pchan->base + DMA_CHAN_CUR_CNT);
+	}
+
+	spin_unlock_irqrestore(&vchan->vc.lock, flags);
+
+	dma_set_residue(state, bytes);
+
+	return ret;
+}
+
+static void sun6i_dma_issue_pending(struct dma_chan *chan)
+{
+	struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
+	struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
+	unsigned long flags;
+
+	spin_lock_irqsave(&vchan->vc.lock, flags);
+
+	if (vchan_issue_pending(&vchan->vc)) {
+		spin_lock(&sdev->lock);
+
+		if (!vchan->phy && list_empty(&vchan->node)) {
+			list_add_tail(&vchan->node, &sdev->pending);
+			tasklet_schedule(&sdev->task);
+			dev_dbg(chan2dev(chan), "vchan %p: issued\n",
+				&vchan->vc);
+		}
+
+		spin_unlock(&sdev->lock);
+	} else {
+		dev_dbg(chan2dev(chan), "vchan %p: nothing to issue\n",
+			&vchan->vc);
+	}
+
+	spin_unlock_irqrestore(&vchan->vc.lock, flags);
+}
+
+static int sun6i_dma_alloc_chan_resources(struct dma_chan *chan)
+{
+	return 0;
+}
+
+static void sun6i_dma_free_chan_resources(struct dma_chan *chan)
+{
+	struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
+	struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
+	unsigned long flags;
+
+	spin_lock_irqsave(&sdev->lock, flags);
+	list_del_init(&vchan->node);
+	spin_unlock_irqrestore(&sdev->lock, flags);
+
+	vchan_free_chan_resources(&vchan->vc);
+}
+
+static struct dma_chan *sun6i_dma_of_xlate(struct of_phandle_args *dma_spec,
+					   struct of_dma *ofdma)
+{
+	struct sun6i_dma_dev *sdev = ofdma->of_dma_data;
+	struct sun6i_vchan *vchan;
+	struct dma_chan *chan;
+	u8 port = dma_spec->args[0];
+
+	if (port > NR_MAX_REQUESTS)
+		return NULL;
+
+	chan = dma_get_any_slave_channel(&sdev->slave);
+	if (!chan)
+		return NULL;
+
+	vchan = to_sun6i_vchan(chan);
+	vchan->port = port;
+
+	return chan;
+}
+
+static inline void sun6i_kill_tasklet(struct sun6i_dma_dev *sdev)
+{
+	/* Disable all interrupts from DMA */
+	writel(0, sdev->base + DMA_IRQ_EN(0));
+	writel(0, sdev->base + DMA_IRQ_EN(1));
+
+	/* Prevent spurious interrupts from scheduling the tasklet */
+	atomic_inc(&sdev->tasklet_shutdown);
+
+	/* Make sure all interrupts are handled */
+	synchronize_irq(sdev->irq);
+
+	/* Actually prevent the tasklet from being scheduled */
+	tasklet_kill(&sdev->task);
+}
+
+static inline void sun6i_dma_free(struct sun6i_dma_dev *sdev)
+{
+	int i;
+
+	for (i = 0; i < NR_MAX_VCHANS; i++) {
+		struct sun6i_vchan *vchan = &sdev->vchans[i];
+
+		list_del(&vchan->vc.chan.device_node);
+		tasklet_kill(&vchan->vc.task);
+	}
+}
+
+static int sun6i_dma_probe(struct platform_device *pdev)
+{
+	struct sun6i_dma_dev *sdc;
+	struct resource *res;
+	struct clk *mux, *pll6;
+	int ret, i;
+
+	sdc = devm_kzalloc(&pdev->dev, sizeof(*sdc), GFP_KERNEL);
+	if (!sdc)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	sdc->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(sdc->base))
+		return PTR_ERR(sdc->base);
+
+	sdc->irq = platform_get_irq(pdev, 0);
+	if (sdc->irq < 0) {
+		dev_err(&pdev->dev, "Cannot claim IRQ\n");
+		return sdc->irq;
+	}
+
+	sdc->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(sdc->clk)) {
+		dev_err(&pdev->dev, "No clock specified\n");
+		return PTR_ERR(sdc->clk);
+	}
+
+	mux = clk_get(NULL, "ahb1_mux");
+	if (IS_ERR(mux)) {
+		dev_err(&pdev->dev, "Couldn't get AHB1 Mux\n");
+		return PTR_ERR(mux);
+	}
+
+	pll6 = clk_get(NULL, "pll6");
+	if (IS_ERR(pll6)) {
+		dev_err(&pdev->dev, "Couldn't get PLL6\n");
+		clk_put(mux);
+		return PTR_ERR(pll6);
+	}
+
+	ret = clk_set_parent(mux, pll6);
+	clk_put(pll6);
+	clk_put(mux);
+
+	if (ret) {
+		dev_err(&pdev->dev, "Couldn't reparent AHB1 on PLL6\n");
+		return ret;
+	}
+
+	sdc->rstc = devm_reset_control_get(&pdev->dev, NULL);
+	if (IS_ERR(sdc->rstc)) {
+		dev_err(&pdev->dev, "No reset controller specified\n");
+		return PTR_ERR(sdc->rstc);
+	}
+
+	sdc->pool = dmam_pool_create(dev_name(&pdev->dev), &pdev->dev,
+				     sizeof(struct sun6i_dma_lli), 4, 0);
+	if (!sdc->pool) {
+		dev_err(&pdev->dev, "No memory for descriptors dma pool\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(pdev, sdc);
+	INIT_LIST_HEAD(&sdc->pending);
+	spin_lock_init(&sdc->lock);
+
+	dma_cap_set(DMA_PRIVATE, sdc->slave.cap_mask);
+	dma_cap_set(DMA_MEMCPY, sdc->slave.cap_mask);
+	dma_cap_set(DMA_SLAVE, sdc->slave.cap_mask);
+
+	INIT_LIST_HEAD(&sdc->slave.channels);
+	sdc->slave.device_alloc_chan_resources	= sun6i_dma_alloc_chan_resources;
+	sdc->slave.device_free_chan_resources	= sun6i_dma_free_chan_resources;
+	sdc->slave.device_tx_status		= sun6i_dma_tx_status;
+	sdc->slave.device_issue_pending		= sun6i_dma_issue_pending;
+	sdc->slave.device_prep_slave_sg		= sun6i_dma_prep_slave_sg;
+	sdc->slave.device_prep_dma_memcpy	= sun6i_dma_prep_dma_memcpy;
+	sdc->slave.device_control		= sun6i_dma_control;
+	sdc->slave.chancnt			= NR_MAX_VCHANS;
+
+	sdc->slave.dev = &pdev->dev;
+
+	sdc->pchans = devm_kcalloc(&pdev->dev, NR_MAX_CHANNELS,
+				   sizeof(struct sun6i_pchan), GFP_KERNEL);
+	if (!sdc->pchans)
+		return -ENOMEM;
+
+	sdc->vchans = devm_kcalloc(&pdev->dev, NR_MAX_VCHANS,
+				   sizeof(struct sun6i_vchan), GFP_KERNEL);
+	if (!sdc->vchans)
+		return -ENOMEM;
+
+	tasklet_init(&sdc->task, sun6i_dma_tasklet, (unsigned long)sdc);
+
+	for (i = 0; i < NR_MAX_CHANNELS; i++) {
+		struct sun6i_pchan *pchan = &sdc->pchans[i];
+
+		pchan->idx = i;
+		pchan->base = sdc->base + 0x100 + i * 0x40;
+	}
+
+	for (i = 0; i < NR_MAX_VCHANS; i++) {
+		struct sun6i_vchan *vchan = &sdc->vchans[i];
+
+		INIT_LIST_HEAD(&vchan->node);
+		vchan->vc.desc_free = sun6i_dma_free_desc;
+		vchan_init(&vchan->vc, &sdc->slave);
+	}
+
+	ret = reset_control_deassert(sdc->rstc);
+	if (ret) {
+		dev_err(&pdev->dev, "Couldn't deassert the device from reset\n");
+		goto err_chan_free;
+	}
+
+	ret = clk_prepare_enable(sdc->clk);
+	if (ret) {
+		dev_err(&pdev->dev, "Couldn't enable the clock\n");
+		goto err_reset_assert;
+	}
+
+	ret = devm_request_irq(&pdev->dev, sdc->irq, sun6i_dma_interrupt, 0,
+			       dev_name(&pdev->dev), sdc);
+	if (ret) {
+		dev_err(&pdev->dev, "Cannot request IRQ\n");
+		goto err_clk_disable;
+	}
+
+	ret = dma_async_device_register(&sdc->slave);
+	if (ret) {
+		dev_warn(&pdev->dev, "Failed to register DMA engine device\n");
+		goto err_irq_disable;
+	}
+
+	ret = of_dma_controller_register(pdev->dev.of_node, sun6i_dma_of_xlate,
+					 sdc);
+	if (ret) {
+		dev_err(&pdev->dev, "of_dma_controller_register failed\n");
+		goto err_dma_unregister;
+	}
+
+	return 0;
+
+err_dma_unregister:
+	dma_async_device_unregister(&sdc->slave);
+err_irq_disable:
+	sun6i_kill_tasklet(sdc);
+err_clk_disable:
+	clk_disable_unprepare(sdc->clk);
+err_reset_assert:
+	reset_control_assert(sdc->rstc);
+err_chan_free:
+	sun6i_dma_free(sdc);
+	return ret;
+}
+
+static int sun6i_dma_remove(struct platform_device *pdev)
+{
+	struct sun6i_dma_dev *sdc = platform_get_drvdata(pdev);
+
+	of_dma_controller_free(pdev->dev.of_node);
+	dma_async_device_unregister(&sdc->slave);
+
+	sun6i_kill_tasklet(sdc);
+
+	clk_disable_unprepare(sdc->clk);
+	reset_control_assert(sdc->rstc);
+
+	sun6i_dma_free(sdc);
+
+	return 0;
+}
+
+static struct of_device_id sun6i_dma_match[] = {
+	{ .compatible = "allwinner,sun6i-a31-dma" },
+	{ /* sentinel */ }
+};
+
+static struct platform_driver sun6i_dma_driver = {
+	.probe		= sun6i_dma_probe,
+	.remove		= sun6i_dma_remove,
+	.driver = {
+		.name		= "sun6i-dma",
+		.of_match_table	= sun6i_dma_match,
+	},
+};
+module_platform_driver(sun6i_dma_driver);
+
+MODULE_DESCRIPTION("Allwinner A31 DMA Controller Driver");
+MODULE_AUTHOR("Sugar <shuge@allwinnertech.com>");
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_LICENSE("GPL");
-- 
2.0.1

^ permalink raw reply related

* [PATCH v2 1/8] dma: sun4i: Add support for the DMA engine on sun[457]i SoCs
From: Maxime Ripard @ 2014-07-17 20:56 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1404619518-7592-2-git-send-email-emilio@elopez.com.ar>

On Sun, Jul 06, 2014 at 01:05:08AM -0300, Emilio L?pez wrote:
> This patch adds support for the DMA engine present on Allwinner A10,
> A13, A10S and A20 SoCs. This engine has two kinds of channels: normal
> and dedicated. The main difference is in the mode of operation;
> while a single normal channel may be operating at any given time,
> dedicated channels may operate simultaneously provided there is no
> overlap of source or destination.
> 
> Hardware documentation can be found on A10 User Manual (section 12), A13
> User Manual (section 14) and A20 User Manual (section 1.12)
> 
> Signed-off-by: Emilio L?pez <emilio@elopez.com.ar>
> ---
> 
> Changes from v1:
>  * address comments from Chen-Yu and Maxime
>  * fix issue converting bus width
>  * switch to using a threaded IRQ instead of a tasklet on
>    recommendation from Maxime
>  * fix issue setting magic timing parameter for SPI transfers
>  * fix an issue with list handling reported by the kbuild 0-DAY robot (thanks!)
>  * drop a lot of unused #define
>  * probably some more stuff I'm forgetting
> 
>  .../devicetree/bindings/dma/sun4i-dma.txt          |   45 +
>  drivers/dma/Kconfig                                |   10 +
>  drivers/dma/Makefile                               |    1 +
>  drivers/dma/sun4i-dma.c                            | 1025 ++++++++++++++++++++
>  4 files changed, 1081 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/dma/sun4i-dma.txt
>  create mode 100644 drivers/dma/sun4i-dma.c
> 
> diff --git a/Documentation/devicetree/bindings/dma/sun4i-dma.txt b/Documentation/devicetree/bindings/dma/sun4i-dma.txt
> new file mode 100644
> index 0000000..f5661a5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dma/sun4i-dma.txt
> @@ -0,0 +1,45 @@
> +Allwinner A10 DMA Controller
> +
> +This driver follows the generic DMA bindings defined in dma.txt.
> +
> +Required properties:
> +
> +- compatible:	Must be "allwinner,sun4i-a10-dma"
> +- reg:		Should contain the registers base address and length
> +- interrupts:	Should contain a reference to the interrupt used by this device
> +- clocks:	Should contain a reference to the parent AHB clock
> +- #dma-cells :	Should be 1, a single cell holding a line request number
> +
> +Example:
> +	dma: dma-controller at 01c02000 {
> +		compatible = "allwinner,sun4i-a10-dma";
> +		reg = <0x01c02000 0x1000>;
> +		interrupts = <27>;
> +		clocks = <&ahb_gates 6>;
> +		#dma-cells = <1>;
> +	};
> +
> +Clients:
> +
> +DMA clients connected to the Allwinner A10 DMA controller must use the
> +format described in the dma.txt file, using a three-cell specifier for
> +each channel: a phandle plus two integer cells.
> +The three cells in order are:
> +
> +1. A phandle pointing to the DMA controller.
> +2. Whether it is using normal (0) or dedicated (1) channels
> +2. The port ID as specified in the datasheet
> +
> +Example:
> +	spi2: spi at 01c17000 {
> +		compatible = "allwinner,sun4i-a10-spi";
> +		reg = <0x01c17000 0x1000>;
> +		interrupts = <0 12 4>;
> +		clocks = <&ahb_gates 22>, <&spi2_clk>;
> +		clock-names = "ahb", "mod";
> +		dmas = <&dma 1 29>, <&dma 1 28>;
> +		dma-names = "rx", "tx";
> +		status = "disabled";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +	};
> diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> index 1eca7b9..cfa6e0a 100644
> --- a/drivers/dma/Kconfig
> +++ b/drivers/dma/Kconfig
> @@ -375,6 +375,16 @@ config XILINX_VDMA
>  	  channels, Memory Mapped to Stream (MM2S) and Stream to
>  	  Memory Mapped (S2MM) for the data transfers.
>  
> +config SUN4I_DMA
> +	tristate "Allwinner A10/A10S/A13/A20 DMA support"

I'm not that fond of having an exhaustive list here. If some other SoC
we didn't thought of or get a new SoC that uses this controller, this
list won't be exhaustive anymore, which is even worse.

Just mention the A10.

> +	depends on (MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || (COMPILE_TEST && OF && ARM))

This pretty much defeats the purpose of COMPILE_TEST

> +	select DMA_ENGINE
> +	select DMA_OF
> +	select DMA_VIRTUAL_CHANNELS

I guess you could default to y for the SoCs where it's relevant.

> +	help
> +	  Enable support for the DMA controller present in the sun4i,
> +	  sun5i and sun7i Allwinner ARM SoCs.
> +
>  config DMA_ENGINE
>  	bool
>  
> diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> index c779e1e..430de61 100644
> --- a/drivers/dma/Makefile
> +++ b/drivers/dma/Makefile
> @@ -47,3 +47,4 @@ obj-$(CONFIG_MOXART_DMA) += moxart-dma.o
>  obj-$(CONFIG_FSL_EDMA) += fsl-edma.o
>  obj-$(CONFIG_QCOM_BAM_DMA) += qcom_bam_dma.o
>  obj-y += xilinx/
> +obj-$(CONFIG_SUN4I_DMA) += sun4i-dma.o
> diff --git a/drivers/dma/sun4i-dma.c b/drivers/dma/sun4i-dma.c
> new file mode 100644
> index 0000000..24fa391
> --- /dev/null
> +++ b/drivers/dma/sun4i-dma.c
> @@ -0,0 +1,1025 @@
> +/*
> + * Copyright (C) 2014 Emilio L?pez
> + * Emilio L?pez <emilio@elopez.com.ar>
> + *
> + * 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.
> + */
> +
> +#include <linux/bitmap.h>
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dmapool.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of_dma.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#include "virt-dma.h"
> +
> +/** Normal DMA register values **/
> +
> +/* Normal DMA source/destination data request type values */
> +#define NDMA_DRQ_TYPE_SDRAM			0x16
> +#define NDMA_DRQ_TYPE_LIMIT			(0x1F + 1)

Hmmm, I'm unsure what this is about... What is it supposed to do, and
how is it different from BIT(5) ?

> +
> +/** Normal DMA register layout **/
> +
> +/* Normal DMA configuration register layout */
> +#define NDMA_CFG_LOADING			BIT(31)
> +#define NDMA_CFG_CONT_MODE			BIT(30)
> +#define NDMA_CFG_WAIT_STATE(n)			((n) << 27)
> +#define NDMA_CFG_DEST_DATA_WIDTH(width)		((width) << 25)
> +#define NDMA_CFG_DEST_BURST_LENGTH(len)		((len) << 23)
> +#define NDMA_CFG_DEST_NON_SECURE		BIT(22)
> +#define NDMA_CFG_DEST_FIXED_ADDR		BIT(21)
> +#define NDMA_CFG_DEST_DRQ_TYPE(type)		((type) << 16)
> +#define NDMA_CFG_BYTE_COUNT_MODE_REMAIN		BIT(15)
> +#define NDMA_CFG_SRC_DATA_WIDTH(width)		((width) << 9)
> +#define NDMA_CFG_SRC_BURST_LENGTH(len)		((len) << 7)
> +#define NDMA_CFG_SRC_NON_SECURE			BIT(6)
> +#define NDMA_CFG_SRC_FIXED_ADDR			BIT(5)
> +#define NDMA_CFG_SRC_DRQ_TYPE(type)		((type) << 0)
> +
> +/** Dedicated DMA register values **/
> +
> +/* Dedicated DMA source/destination address mode values */
> +#define DDMA_ADDR_MODE_LINEAR			0
> +#define DDMA_ADDR_MODE_IO			1
> +#define DDMA_ADDR_MODE_HORIZONTAL_PAGE		2
> +#define DDMA_ADDR_MODE_VERTICAL_PAGE		3
> +
> +/* Dedicated DMA source/destination data request type values */
> +#define DDMA_DRQ_TYPE_SDRAM			0x1
> +#define DDMA_DRQ_TYPE_LIMIT			(0x1F + 1)
> +
> +/** Dedicated DMA register layout **/
> +
> +/* Dedicated DMA configuration register layout */
> +#define DDMA_CFG_LOADING			BIT(31)
> +#define DDMA_CFG_BUSY				BIT(30)
> +#define DDMA_CFG_CONT_MODE			BIT(29)
> +#define DDMA_CFG_DEST_NON_SECURE		BIT(28)
> +#define DDMA_CFG_DEST_DATA_WIDTH(width)		((width) << 25)
> +#define DDMA_CFG_DEST_BURST_LENGTH(len)		((len) << 23)
> +#define DDMA_CFG_DEST_ADDR_MODE(mode)		((mode) << 21)
> +#define DDMA_CFG_DEST_DRQ_TYPE(type)		((type) << 16)
> +#define DDMA_CFG_BYTE_COUNT_MODE_REMAIN		BIT(15)
> +#define DDMA_CFG_SRC_NON_SECURE			BIT(12)
> +#define DDMA_CFG_SRC_DATA_WIDTH(width)		((width) << 9)
> +#define DDMA_CFG_SRC_BURST_LENGTH(len)		((len) << 7)
> +#define DDMA_CFG_SRC_ADDR_MODE(mode)		((mode) << 5)
> +#define DDMA_CFG_SRC_DRQ_TYPE(type)		((type) << 0)
> +
> +/* Dedicated DMA parameter register layout */
> +#define DDMA_PARA_DEST_DATA_BLK_SIZE(n)		(((n) - 1) << 24)
> +#define DDMA_PARA_DEST_WAIT_CYCLES(n)		(((n) - 1) << 16)
> +#define DDMA_PARA_SRC_DATA_BLK_SIZE(n)		(((n) - 1) << 8)
> +#define DDMA_PARA_SRC_WAIT_CYCLES(n)		(((n) - 1) << 0)
> +
> +/** DMA register offsets **/
> +
> +/* General register offsets */
> +#define DMA_IRQ_ENABLE_REG			0x0
> +#define DMA_IRQ_PENDING_STATUS_REG		0x4
> +
> +/* Normal DMA register offsets */
> +#define NDMA_CHANNEL_REG_BASE(n)		(0x100 + (n) * 0x20)
> +#define NDMA_CFG_REG				0x0
> +#define NDMA_SRC_ADDR_REG			0x4
> +#define NDMA_DEST_ADDR_REG			0x8
> +#define NDMA_BYTE_COUNT_REG			0xC
> +
> +/* Dedicated DMA register offsets */
> +#define DDMA_CHANNEL_REG_BASE(n)		(0x300 + (n) * 0x20)
> +#define DDMA_CFG_REG				0x0
> +#define DDMA_SRC_ADDR_REG			0x4
> +#define DDMA_DEST_ADDR_REG			0x8
> +#define DDMA_BYTE_COUNT_REG			0xC
> +#define DDMA_PARA_REG				0x18
> +
> +/** DMA Driver **/
> +
> +/*
> + * Normal DMA has 8 channels, and Dedicated DMA has another 8, so that's
> + * 16 channels. As for endpoints, there's 29 and 21 respectively. Given
> + * that the Normal DMA endpoints (other than SDRAM) can be used as tx/rx,
> + * we need 78 vchans in total
> + */
> +#define NDMA_NR_MAX_CHANNELS	8
> +#define DDMA_NR_MAX_CHANNELS	8
> +#define DMA_NR_MAX_CHANNELS	(NDMA_NR_MAX_CHANNELS + DDMA_NR_MAX_CHANNELS)
> +#define NDMA_NR_MAX_VCHANS	(29 * 2 - 1)
> +#define DDMA_NR_MAX_VCHANS	21
> +#define DMA_NR_MAX_VCHANS	(NDMA_NR_MAX_VCHANS + DDMA_NR_MAX_VCHANS)
> +
> +/* This set of DDMA timing parameters were found experimentally while
> + * working with the SPI driver and seem to make it behave correctly */
> +#define DDMA_MAGIC_SPI_PARAMETERS	(DDMA_PARA_DEST_DATA_BLK_SIZE(1) | \
> +					DDMA_PARA_SRC_DATA_BLK_SIZE(1) | \
> +					DDMA_PARA_DEST_WAIT_CYCLES(2) | \
> +					DDMA_PARA_SRC_WAIT_CYCLES(2))
> +
> +struct sun4i_dma_pchan {
> +	/* Register base of channel */
> +	void __iomem			*base;
> +	/* vchan currently being serviced */
> +	struct sun4i_dma_vchan		*vchan;
> +	/* Is this a dedicated pchan? */
> +	int				is_dedicated;
> +};
> +
> +struct sun4i_dma_vchan {
> +	struct virt_dma_chan		vc;
> +	struct dma_slave_config		cfg;
> +	struct sun4i_dma_pchan		*pchan;
> +	struct sun4i_dma_promise	*processing;
> +	struct sun4i_dma_contract	*contract;
> +	u8				endpoint;
> +	int				is_dedicated;
> +};
> +
> +struct sun4i_dma_promise {
> +	u32				cfg;
> +	u32				para;
> +	dma_addr_t			src;
> +	dma_addr_t			dst;
> +	size_t				len;
> +	struct list_head		list;
> +};
> +
> +/* A contract is a set of promises */
> +struct sun4i_dma_contract {
> +	struct virt_dma_desc		vd;
> +	struct list_head		demands;
> +	struct list_head		completed_demands;
> +};
> +
> +struct sun4i_dma_dev {
> +	DECLARE_BITMAP(pchans_used, DMA_NR_MAX_CHANNELS);
> +	struct dma_device		slave;
> +	struct sun4i_dma_pchan		*pchans;
> +	struct sun4i_dma_vchan		*vchans;
> +	void __iomem			*base;
> +	struct clk			*clk;
> +	int				irq;
> +	spinlock_t			lock;
> +};
> +
> +static struct sun4i_dma_dev *to_sun4i_dma_dev(struct dma_device *dev)
> +{
> +	return container_of(dev, struct sun4i_dma_dev, slave);
> +}
> +
> +static struct sun4i_dma_vchan *to_sun4i_dma_vchan(struct dma_chan *chan)
> +{
> +	return container_of(chan, struct sun4i_dma_vchan, vc.chan);
> +}
> +
> +static struct sun4i_dma_contract *to_sun4i_dma_contract(struct virt_dma_desc *vd)
> +{
> +	return container_of(vd, struct sun4i_dma_contract, vd);
> +}
> +
> +static struct device *chan2dev(struct dma_chan *chan)
> +{
> +	return &chan->dev->device;
> +}
> +
> +static int convert_burst(u32 maxburst)
> +{
> +	if (maxburst > 8)
> +		return -EINVAL;
> +
> +	/* 1 -> 0, 4 -> 1, 8 -> 2 */
> +	return (maxburst >> 2);
> +}
> +
> +static int convert_buswidth(enum dma_slave_buswidth addr_width)
> +{
> +	if (addr_width > DMA_SLAVE_BUSWIDTH_4_BYTES)
> +		return -EINVAL;
> +
> +	/* 8 (1 byte) -> 0, 16 (2 bytes) -> 1, 32 (4 bytes) -> 2 */
> +	return (addr_width >> 1);
> +}
> +
> +static int sun4i_dma_alloc_chan_resources(struct dma_chan *chan)
> +{
> +	return 0;
> +}
> +
> +static void sun4i_dma_free_chan_resources(struct dma_chan *chan)
> +{
> +	struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
> +
> +	vchan_free_chan_resources(&vchan->vc);
> +}
> +
> +static struct sun4i_dma_pchan *find_and_use_pchan(struct sun4i_dma_dev *priv,
> +						  struct sun4i_dma_vchan *vchan)
> +{
> +	struct sun4i_dma_pchan *pchan = NULL, *pchans = priv->pchans;
> +	unsigned long flags;
> +	int i, max;
> +
> +	spin_lock_irqsave(&priv->lock, flags);
> +
> +	/*
> +	 * pchans 0-NDMA_NR_MAX_CHANNELS are normal, and
> +	 * NDMA_NR_MAX_CHANNELS+ are dedicated ones
> +	 */
> +	if (vchan->is_dedicated) {
> +		i = NDMA_NR_MAX_CHANNELS;
> +		max = DMA_NR_MAX_CHANNELS;
> +	} else {
> +		i = 0;
> +		max = NDMA_NR_MAX_CHANNELS;
> +	}
> +
> +	for_each_clear_bit_from(i, &priv->pchans_used, max) {
> +		pchan = &pchans[i];
> +		pchan->vchan = vchan;
> +		set_bit(i, priv->pchans_used);
> +		break;
> +	}
> +
> +	spin_unlock_irqrestore(&priv->lock, flags);
> +
> +	return pchan;
> +}
> +
> +static void release_pchan(struct sun4i_dma_dev *priv,
> +			  struct sun4i_dma_pchan *pchan)
> +{
> +	unsigned long flags;
> +	int nr = pchan - priv->pchans;
> +
> +	spin_lock_irqsave(&priv->lock, flags);
> +
> +	clear_bit(nr, priv->pchans_used);
> +	pchan->vchan = NULL;
> +
> +	spin_unlock_irqrestore(&priv->lock, flags);
> +}
> +
> +static void configure_pchan(struct sun4i_dma_pchan *pchan,
> +			    struct sun4i_dma_promise *d)
> +{
> +	/*
> +	 * Configure addresses and misc parameters depending on type
> +	 * DDMA has an extra field with timing parameters
> +	 */
> +	if (pchan->is_dedicated) {
> +		writel_relaxed(d->src, pchan->base + DDMA_SRC_ADDR_REG);
> +		writel_relaxed(d->dst, pchan->base + DDMA_DEST_ADDR_REG);
> +		writel_relaxed(d->len, pchan->base + DDMA_BYTE_COUNT_REG);
> +		writel_relaxed(d->para, pchan->base + DDMA_PARA_REG);
> +		writel_relaxed(d->cfg, pchan->base + DDMA_CFG_REG);
> +	} else {
> +		writel_relaxed(d->src, pchan->base + NDMA_SRC_ADDR_REG);
> +		writel_relaxed(d->dst, pchan->base + NDMA_DEST_ADDR_REG);
> +		writel_relaxed(d->len, pchan->base + NDMA_BYTE_COUNT_REG);
> +		writel_relaxed(d->cfg, pchan->base + NDMA_CFG_REG);
> +	}
> +}
> +
> +static void set_pchan_interrupt(struct sun4i_dma_dev *priv,
> +				struct sun4i_dma_pchan *pchan,
> +				int half, int end)
> +{
> +	u32 reg = readl_relaxed(priv->base + DMA_IRQ_ENABLE_REG);
> +	int pchan_number = pchan - priv->pchans;
> +
> +	if (half)
> +		reg |= BIT(pchan_number * 2);
> +	else
> +		reg &= ~BIT(pchan_number * 2);
> +
> +	if (end)
> +		reg |= BIT(pchan_number * 2 + 1);
> +	else
> +		reg &= ~BIT(pchan_number * 2 + 1);
> +
> +	writel_relaxed(reg, priv->base + DMA_IRQ_ENABLE_REG);

I don't see any interrupts here. Is this suppose to be called with a
lock taken? If so, it should be mentionned in some comment.

> +}
> +
> +static int execute_vchan_pending(struct sun4i_dma_dev *priv,
> +				 struct sun4i_dma_vchan *vchan)
> +{
> +	struct sun4i_dma_promise *promise = NULL;
> +	struct sun4i_dma_contract *contract = NULL;
> +	struct sun4i_dma_pchan *pchan;
> +	struct virt_dma_desc *vd;
> +	int ret;
> +
> +	lockdep_assert_held(&vchan->vc.lock);
> +
> +	/* We need a pchan to do anything, so secure one if available */
> +	pchan = find_and_use_pchan(priv, vchan);
> +	if (!pchan)
> +		return -EBUSY;
> +
> +	/*
> +	 * Channel endpoints must not be repeated, so if this vchan
> +	 * has already submitted some work, we can't do anything else
> +	 */
> +	if (vchan->processing) {
> +		dev_dbg(chan2dev(&vchan->vc.chan),
> +			"processing something to this endpoint already\n");
> +		ret = -EBUSY;
> +		goto release_pchan;
> +	}
> +
> +	do {
> +		/* Figure out which contract we're working with today */
> +		vd = vchan_next_desc(&vchan->vc);
> +		if (!vd) {
> +			dev_dbg(chan2dev(&vchan->vc.chan),
> +				"No pending contract found");
> +			ret = 0;
> +			goto release_pchan;
> +		}
> +
> +		contract = to_sun4i_dma_contract(vd);
> +		if (list_empty(&contract->demands)) {
> +			/* The contract has been completed so mark it as such */
> +			list_del(&contract->vd.node);
> +			vchan_cookie_complete(&contract->vd);
> +			dev_dbg(chan2dev(&vchan->vc.chan),
> +				"Empty contract found and marked complete");
> +		}
> +	} while (list_empty(&contract->demands));
> +
> +	/* Now find out what we need to do */
> +	promise = list_first_entry(&contract->demands,
> +				   struct sun4i_dma_promise, list);
> +	vchan->processing = promise;
> +
> +	/* ... and make it reality */
> +	if (promise) {
> +		vchan->contract = contract;
> +		set_pchan_interrupt(priv, pchan, 0, 1);
> +		configure_pchan(pchan, promise);
> +	}
> +
> +	return 0;
> +
> +release_pchan:
> +	release_pchan(priv, pchan);
> +	return ret;
> +}
> +
> +/**
> + * Generate a promise, to be used in a normal DMA contract.
> + *
> + * A NDMA promise contains all the information required to program the
> + * normal part of the DMA Engine and get data copied. A non-executed
> + * promise will live in the demands list on a contract. Once it has been
> + * completed, it will be moved to the completed demands list for later freeing.
> + * All linked promises will be freed when the corresponding contract is freed
> + */
> +static struct sun4i_dma_promise *
> +generate_ndma_promise(struct dma_chan *chan, dma_addr_t src, dma_addr_t dest,
> +		      size_t len, struct dma_slave_config *sconfig)
> +{
> +	struct sun4i_dma_promise *promise;
> +	int ret;
> +
> +	promise = kzalloc(sizeof(*promise), GFP_NOWAIT);
> +	if (!promise)
> +		return NULL;
> +
> +	promise->src = src;
> +	promise->dst = dest;
> +	promise->len = len;
> +	promise->cfg = NDMA_CFG_LOADING | NDMA_CFG_BYTE_COUNT_MODE_REMAIN;
> +
> +	/* Source burst */
> +	ret = convert_burst(sconfig->src_maxburst);
> +	if (IS_ERR_VALUE(ret))
> +		goto fail;
> +	promise->cfg |= NDMA_CFG_SRC_BURST_LENGTH(ret);
> +
> +	/* Destination burst */
> +	ret = convert_burst(sconfig->dst_maxburst);
> +	if (IS_ERR_VALUE(ret))
> +		goto fail;
> +	promise->cfg |= NDMA_CFG_DEST_BURST_LENGTH(ret);
> +
> +	/* Source bus width */
> +	ret = convert_buswidth(sconfig->src_addr_width);
> +	if (IS_ERR_VALUE(ret))
> +		goto fail;
> +	promise->cfg |= NDMA_CFG_SRC_DATA_WIDTH(ret);
> +
> +	/* Destination bus width */
> +	ret = convert_buswidth(sconfig->dst_addr_width);
> +	if (IS_ERR_VALUE(ret))
> +		goto fail;
> +	promise->cfg |= NDMA_CFG_DEST_DATA_WIDTH(ret);
> +
> +	return promise;
> +
> +fail:
> +	kfree(promise);
> +	return NULL;
> +}
> +
> +/**
> + * Generate a promise, to be used in a dedicated DMA contract.
> + *
> + * A DDMA promise contains all the information required to program the
> + * Dedicated part of the DMA Engine and get data copied. A non-executed
> + * promise will live in the demands list on a contract. Once it has been
> + * completed, it will be moved to the completed demands list for later freeing.
> + * All linked promises will be freed when the corresponding contract is freed
> + */
> +static struct sun4i_dma_promise *
> +generate_ddma_promise(struct dma_chan *chan, dma_addr_t src, dma_addr_t dest,
> +		      size_t len, struct dma_slave_config *sconfig)
> +{
> +	struct sun4i_dma_promise *promise;
> +	int ret;
> +
> +	promise = kzalloc(sizeof(*promise), GFP_NOWAIT);
> +	if (!promise)
> +		return NULL;
> +
> +	promise->src = src;
> +	promise->dst = dest;
> +	promise->len = len;
> +	promise->cfg = DDMA_CFG_LOADING | DDMA_CFG_BYTE_COUNT_MODE_REMAIN;
> +
> +	/* Source burst */
> +	ret = convert_burst(sconfig->src_maxburst);
> +	if (IS_ERR_VALUE(ret))
> +		goto fail;
> +	promise->cfg |= DDMA_CFG_SRC_BURST_LENGTH(ret);
> +
> +	/* Destination burst */
> +	ret = convert_burst(sconfig->dst_maxburst);
> +	if (IS_ERR_VALUE(ret))
> +		goto fail;
> +	promise->cfg |= DDMA_CFG_DEST_BURST_LENGTH(ret);
> +
> +	/* Source bus width */
> +	ret = convert_buswidth(sconfig->src_addr_width);
> +	if (IS_ERR_VALUE(ret))
> +		goto fail;
> +	promise->cfg |= DDMA_CFG_SRC_DATA_WIDTH(ret);
> +
> +	/* Destination bus width */
> +	ret = convert_buswidth(sconfig->dst_addr_width);
> +	if (IS_ERR_VALUE(ret))
> +		goto fail;
> +	promise->cfg |= DDMA_CFG_DEST_DATA_WIDTH(ret);
> +
> +	return promise;
> +
> +fail:
> +	kfree(promise);
> +	return NULL;
> +}
> +
> +/**
> + * Generate a contract
> + *
> + * Contracts function as DMA descriptors. As our hardware does not support
> + * linked lists, we need to implement SG via software. We use a contract
> + * to hold all the pieces of the request and process them serially one
> + * after another. Each piece is represented as a promise.
> + */
> +static struct sun4i_dma_contract *generate_dma_contract(void)
> +{
> +	struct sun4i_dma_contract *contract;
> +
> +	contract = kzalloc(sizeof(*contract), GFP_NOWAIT);
> +	if (!contract)
> +		return NULL;
> +
> +	INIT_LIST_HEAD(&contract->demands);
> +	INIT_LIST_HEAD(&contract->completed_demands);
> +
> +	return contract;
> +}
> +
> +/**
> + * Free a contract and all its associated promises
> + */
> +static void sun4i_dma_free_contract(struct virt_dma_desc *vd)
> +{
> +	struct sun4i_dma_contract *contract = to_sun4i_dma_contract(vd);
> +	struct sun4i_dma_promise *promise;
> +
> +	/* Free all the demands and completed demands */
> +	list_for_each_entry(promise, &contract->demands, list) {
> +		kfree(promise);
> +	}
> +
> +	list_for_each_entry(promise, &contract->completed_demands, list) {
> +		kfree(promise);
> +	}
>

Those brackets are useless.

> +	kfree(contract);
> +}
> +
> +static struct dma_async_tx_descriptor *
> +sun4i_dma_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest,
> +			  dma_addr_t src, size_t len, unsigned long flags)
> +{
> +	struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
> +	struct dma_slave_config *sconfig = &vchan->cfg;
> +	struct sun4i_dma_promise *promise;
> +	struct sun4i_dma_contract *contract;
> +
> +	contract = generate_dma_contract();
> +	if (!contract)
> +		return NULL;
> +
> +	if (vchan->is_dedicated)
> +		promise = generate_ddma_promise(chan, src, dest, len, sconfig);
> +	else
> +		promise = generate_ndma_promise(chan, src, dest, len, sconfig);
> +
> +	if (!promise) {
> +		kfree(contract);
> +		return NULL;
> +	}
> +
> +	/* Configure memcpy mode */
> +	if (vchan->is_dedicated) {
> +		promise->cfg |= DDMA_CFG_SRC_DRQ_TYPE(DDMA_DRQ_TYPE_SDRAM) |
> +				DDMA_CFG_DEST_DRQ_TYPE(DDMA_DRQ_TYPE_SDRAM);
> +	} else {
> +		promise->cfg |= NDMA_CFG_SRC_DRQ_TYPE(NDMA_DRQ_TYPE_SDRAM) |
> +				NDMA_CFG_DEST_DRQ_TYPE(NDMA_DRQ_TYPE_SDRAM);
> +	}
> +
> +	/* Fill the contract with our only promise */
> +	list_add_tail(&promise->list, &contract->demands);
> +
> +	/* And add it to the vchan */
> +	return vchan_tx_prep(&vchan->vc, &contract->vd, flags);
> +}
> +
> +static struct dma_async_tx_descriptor *
> +sun4i_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
> +			unsigned int sg_len, enum dma_transfer_direction dir,
> +			unsigned long flags, void *context)
> +{
> +	struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
> +	struct dma_slave_config *sconfig = &vchan->cfg;
> +	struct sun4i_dma_promise *promise;
> +	struct sun4i_dma_contract *contract;
> +	struct scatterlist *sg;
> +	dma_addr_t srcaddr, dstaddr;
> +	u32 endpoints, para;
> +	int i;
> +
> +	if (!sgl)
> +		return NULL;
> +
> +	if (!is_slave_direction(dir)) {
> +		dev_err(chan2dev(chan), "Invalid DMA direction\n");
> +		return NULL;
> +	}
> +
> +	contract = generate_dma_contract();
> +	if (!contract)
> +		return NULL;
> +
> +	/* Figure out endpoints */
> +	if (vchan->is_dedicated && dir == DMA_MEM_TO_DEV) {
> +		endpoints = DDMA_CFG_SRC_DRQ_TYPE(DDMA_DRQ_TYPE_SDRAM) |
> +			    DDMA_CFG_SRC_ADDR_MODE(DDMA_ADDR_MODE_LINEAR) |
> +			    DDMA_CFG_DEST_DRQ_TYPE(vchan->endpoint) |
> +			    DDMA_CFG_DEST_ADDR_MODE(DDMA_ADDR_MODE_IO);
> +	} else if (!vchan->is_dedicated && dir == DMA_MEM_TO_DEV) {
> +		endpoints = NDMA_CFG_SRC_DRQ_TYPE(NDMA_DRQ_TYPE_SDRAM) |
> +			    NDMA_CFG_DEST_DRQ_TYPE(vchan->endpoint) |
> +			    NDMA_CFG_DEST_FIXED_ADDR;
> +	} else if (vchan->is_dedicated) {
> +		endpoints = DDMA_CFG_SRC_DRQ_TYPE(vchan->endpoint) |
> +			    DDMA_CFG_SRC_ADDR_MODE(DDMA_ADDR_MODE_IO) |
> +			    DDMA_CFG_DEST_DRQ_TYPE(DDMA_DRQ_TYPE_SDRAM) |
> +			    DDMA_CFG_DEST_ADDR_MODE(DDMA_ADDR_MODE_LINEAR);
> +	} else {
> +		endpoints = NDMA_CFG_SRC_DRQ_TYPE(vchan->endpoint) |
> +			    NDMA_CFG_SRC_FIXED_ADDR |
> +			    NDMA_CFG_DEST_DRQ_TYPE(NDMA_DRQ_TYPE_SDRAM);
> +	}
> +
> +	for_each_sg(sgl, sg, sg_len, i) {
> +		/* Figure out addresses */
> +		if (dir == DMA_MEM_TO_DEV) {
> +			srcaddr = sg_dma_address(sg);
> +			dstaddr = sconfig->dst_addr;
> +		} else {
> +			srcaddr = sconfig->src_addr;
> +			dstaddr = sg_dma_address(sg);
> +		}
> +
> +		/* TODO: should this be configurable? */
> +		para = DDMA_MAGIC_SPI_PARAMETERS;

What is it? Is it supposed to change from one client device to
another?

> +
> +		/* And make a suitable promise */
> +		if (vchan->is_dedicated)
> +			promise = generate_ddma_promise(chan, srcaddr, dstaddr,
> +							sg_dma_len(sg), sconfig);
> +		else
> +			promise = generate_ndma_promise(chan, srcaddr, dstaddr,
> +							sg_dma_len(sg), sconfig);
> +
> +		if (!promise)
> +			return NULL; /* TODO */

TODO what?

> +
> +		promise->cfg |= endpoints;
> +		promise->para = para;
> +
> +		/* Then add it to the contract */
> +		list_add_tail(&promise->list, &contract->demands);
> +	}
> +
> +	/*
> +	 * Once we've got all the promises ready, add the contract
> +	 * to the pending list on the vchan
> +	 */
> +	return vchan_tx_prep(&vchan->vc, &contract->vd, flags);
> +}
> +
> +static int sun4i_dma_terminate_all(struct sun4i_dma_vchan *vchan)
> +{
> +	struct sun4i_dma_pchan *pchan = vchan->pchan;
> +	LIST_HEAD(head);
> +	unsigned long flags, timeout;
> +	u32 d_busy = DDMA_CFG_LOADING | DDMA_CFG_BUSY;
> +	u32 n_busy = NDMA_CFG_LOADING;
> +
> +
> +	spin_lock_irqsave(&vchan->vc.lock, flags);
> +	vchan_get_all_descriptors(&vchan->vc, &head);
> +	spin_unlock_irqrestore(&vchan->vc.lock, flags);
> +
> +	/* If this vchan is operating, wait until it's no longer busy */
> +	if (pchan) {
> +		timeout = jiffies + msecs_to_jiffies(2000);
> +		if (pchan->is_dedicated) {
> +			while (readl(pchan->base + DDMA_CFG_REG) & d_busy)
> +				if (time_after(jiffies, timeout))
> +					return -ETIMEDOUT;
> +		} else {
> +			while (readl(pchan->base + NDMA_CFG_REG) & n_busy)
> +				if (time_after(jiffies, timeout))
> +					return -ETIMEDOUT;
> +		}
> +	}
> +
> +	spin_lock_irqsave(&vchan->vc.lock, flags);
> +	vchan_dma_desc_free_list(&vchan->vc, &head);
> +	spin_unlock_irqrestore(&vchan->vc.lock, flags);
> +
> +	return 0;
> +}
> +
> +static int sun4i_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
> +			     unsigned long arg)
> +{
> +	struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
> +	int ret = 0;
> +
> +	switch (cmd) {
> +	case DMA_RESUME:
> +	case DMA_PAUSE:
> +		ret = -EINVAL;
> +		break;
> +
> +	case DMA_TERMINATE_ALL:
> +		dev_dbg(chan2dev(chan), "Terminating everything on channel\n");
> +		ret = sun4i_dma_terminate_all(vchan);
> +		break;
> +
> +	case DMA_SLAVE_CONFIG:
> +		memcpy(&vchan->cfg, (void *)arg, sizeof(vchan->cfg));
> +		break;
> +
> +	default:
> +		ret = -ENXIO;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static struct dma_chan *sun4i_dma_of_xlate(struct of_phandle_args *dma_spec,
> +					   struct of_dma *ofdma)
> +{
> +	struct sun4i_dma_dev *priv = ofdma->of_dma_data;
> +	struct sun4i_dma_vchan *vchan;
> +	struct dma_chan *chan;
> +	u8 is_dedicated = dma_spec->args[0];
> +	u8 endpoint = dma_spec->args[1];
> +
> +	/* Check if type is Normal or Dedicated */
> +	if (is_dedicated != 0 && is_dedicated != 1)
> +		return NULL;
> +
> +	/* Make sure the endpoint looks sane */
> +	if ((is_dedicated && endpoint >= DDMA_DRQ_TYPE_LIMIT) ||
> +	    (!is_dedicated && endpoint >= NDMA_DRQ_TYPE_LIMIT))
> +		return NULL;
> +
> +	chan = dma_get_any_slave_channel(&priv->slave);
> +	if (!chan)
> +		return NULL;
> +
> +	/* Assign the endpoint to the vchan */
> +	vchan = to_sun4i_dma_vchan(chan);
> +	vchan->is_dedicated = is_dedicated;
> +	vchan->endpoint = endpoint;
> +
> +	return chan;
> +}
> +
> +static enum dma_status sun4i_dma_tx_status(struct dma_chan *chan,
> +					   dma_cookie_t cookie,
> +					   struct dma_tx_state *state)
> +{
> +	struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
> +	struct sun4i_dma_pchan *pchan = vchan->pchan;
> +	struct sun4i_dma_contract *contract;
> +	struct sun4i_dma_promise *promise;
> +	struct virt_dma_desc *vd;
> +	unsigned long flags;
> +	enum dma_status ret;
> +	size_t bytes = 0;
> +
> +	ret = dma_cookie_status(chan, cookie, state);
> +	if (ret == DMA_COMPLETE)
> +		return ret;
> +
> +	spin_lock_irqsave(&vchan->vc.lock, flags);
> +	vd = vchan_find_desc(&vchan->vc, cookie);
> +	if (!vd) /* TODO */

TODO what?

> +		goto exit;
> +	contract = to_sun4i_dma_contract(vd);
> +
> +	list_for_each_entry(promise, &contract->demands, list) {
> +		bytes += promise->len;
> +	}

Useless brackets

> +	/*
> +	 * The hardware is configured to return the remaining byte
> +	 * quantity. If possible, replace the first listed element's
> +	 * full size with the actual remaining amount
> +	 */
> +	promise = list_first_entry_or_null(&contract->demands,
> +					   struct sun4i_dma_promise, list);
> +	if (promise && pchan) {
> +		bytes -= promise->len;
> +		if (pchan->is_dedicated)
> +			bytes += readl(pchan->base + DDMA_BYTE_COUNT_REG);
> +		else
> +			bytes += readl(pchan->base + NDMA_BYTE_COUNT_REG);
> +	}
> +
> +exit:
> +
> +	dma_set_residue(state, bytes);
> +	spin_unlock_irqrestore(&vchan->vc.lock, flags);
> +
> +	return ret;
> +}
> +
> +static void sun4i_dma_issue_pending(struct dma_chan *chan)
> +{
> +	struct sun4i_dma_dev *priv = to_sun4i_dma_dev(chan->device);
> +	struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&vchan->vc.lock, flags);
> +
> +	/*
> +	 * If there are pending transactions for this vchan, push one of
> +	 * them into the engine to get the ball rolling.
> +	 */
> +	if (vchan_issue_pending(&vchan->vc))
> +		execute_vchan_pending(priv, vchan);
> +
> +	spin_unlock_irqrestore(&vchan->vc.lock, flags);
> +}
> +
> +static irqreturn_t sun4i_dma_interrupt(int irq, void *dev_id)
> +{
> +	struct sun4i_dma_dev *priv = dev_id;
> +	struct sun4i_dma_pchan *pchans = priv->pchans, *pchan;
> +	struct sun4i_dma_vchan *vchan;
> +	struct sun4i_dma_contract *contract;
> +	unsigned long pendirq, irqs;
> +	int bit;
> +
> +	pendirq = readl_relaxed(priv->base + DMA_IRQ_PENDING_STATUS_REG);
> +	irqs = readl_relaxed(priv->base + DMA_IRQ_ENABLE_REG);
> +
> +	for_each_set_bit(bit, &pendirq, 32) {
> +		pchan = &pchans[bit >> 1];
> +		vchan = pchan->vchan;
> +		contract = vchan->contract;
> +
> +		/*
> +		 * Disable the IRQ and free the pchan if it's an end
> +		 * interrupt (odd bit)
> +		 */
> +		if (bit & 1) {
> +			spin_lock(&vchan->vc.lock);
> +			/*
> +			 * Move the promise into the completed list now that
> +			 * we're done with it
> +			 */
> +			list_del(&vchan->processing->list);
> +			list_add_tail(&vchan->processing->list, &contract->completed_demands);
> +			vchan->processing = NULL;
> +			vchan->pchan = NULL;
> +			spin_unlock(&vchan->vc.lock);
> +
> +			irqs &= ~BIT(bit);
> +			release_pchan(priv, pchan);
> +		}
> +	}
> +
> +	writel_relaxed(irqs, priv->base + DMA_IRQ_ENABLE_REG);
> +
> +	/* Writing 1 to the pending field will clear the pending interrupt */
> +	writel_relaxed(pendirq, priv->base + DMA_IRQ_PENDING_STATUS_REG);
> +
> +	return IRQ_WAKE_THREAD;
> +}
> +
> +static irqreturn_t sun4i_dma_submit_work(int irq, void *dev_id)
> +{
> +	struct sun4i_dma_dev *priv = dev_id;
> +	struct sun4i_dma_vchan *vchan;
> +	int i;
> +
> +	for (i = 0; i < DMA_NR_MAX_VCHANS; i++) {
> +		vchan = &priv->vchans[i];
> +		spin_lock_irq(&vchan->vc.lock);
> +		execute_vchan_pending(priv, vchan);
> +		spin_unlock_irq(&vchan->vc.lock);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int sun4i_dma_probe(struct platform_device *pdev)
> +{
> +	struct sun4i_dma_dev *priv;
> +	struct resource *res;
> +	int i, j, ret;
> +
> +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	priv->base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(priv->base))
> +		return PTR_ERR(priv->base);
> +
> +	priv->irq = platform_get_irq(pdev, 0);
> +	if (priv->irq < 0) {
> +		dev_err(&pdev->dev, "Cannot claim IRQ\n");
> +		return priv->irq;
> +	}
> +
> +	priv->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(priv->clk)) {
> +		dev_err(&pdev->dev, "No clock specified\n");
> +		return PTR_ERR(priv->clk);
> +	}
> +
> +	platform_set_drvdata(pdev, priv);
> +	spin_lock_init(&priv->lock);
> +
> +	dma_cap_zero(priv->slave.cap_mask);
> +	dma_cap_set(DMA_PRIVATE, priv->slave.cap_mask);
> +	dma_cap_set(DMA_MEMCPY, priv->slave.cap_mask);
> +	dma_cap_set(DMA_SLAVE, priv->slave.cap_mask);
> +
> +	INIT_LIST_HEAD(&priv->slave.channels);
> +	priv->slave.device_alloc_chan_resources	= sun4i_dma_alloc_chan_resources;
> +	priv->slave.device_free_chan_resources	= sun4i_dma_free_chan_resources;
> +	priv->slave.device_tx_status		= sun4i_dma_tx_status;
> +	priv->slave.device_issue_pending	= sun4i_dma_issue_pending;
> +	priv->slave.device_prep_slave_sg	= sun4i_dma_prep_slave_sg;
> +	priv->slave.device_prep_dma_memcpy	= sun4i_dma_prep_dma_memcpy;
> +	priv->slave.device_control		= sun4i_dma_control;
> +	priv->slave.chancnt			= DDMA_NR_MAX_VCHANS;
> +
> +	priv->slave.dev = &pdev->dev;
> +
> +	priv->pchans = devm_kcalloc(&pdev->dev, DMA_NR_MAX_CHANNELS,
> +				    sizeof(struct sun4i_dma_pchan), GFP_KERNEL);
> +	priv->vchans = devm_kcalloc(&pdev->dev, DMA_NR_MAX_VCHANS,
> +				    sizeof(struct sun4i_dma_vchan), GFP_KERNEL);
> +	if (!priv->vchans || !priv->pchans)
> +		return -ENOMEM;
> +
> +	/*
> +	 * [0..NDMA_NR_MAX_CHANNELS) are normal pchans, and
> +	 * [NDMA_NR_MAX_CHANNELS..DMA_NR_MAX_CHANNELS) are dedicated ones
> +	 */
> +	for (i = 0; i < NDMA_NR_MAX_CHANNELS; i++)
> +		priv->pchans[i].base = priv->base + NDMA_CHANNEL_REG_BASE(i);
> +
> +	for (j = 0; i < DMA_NR_MAX_CHANNELS; i++, j++) {
> +		priv->pchans[i].base = priv->base + DDMA_CHANNEL_REG_BASE(j);
> +		priv->pchans[i].is_dedicated = 1;
> +	}
> +
> +	for (i = 0; i < DMA_NR_MAX_VCHANS; i++) {
> +		struct sun4i_dma_vchan *vchan = &priv->vchans[i];
> +
> +		spin_lock_init(&vchan->vc.lock);
> +		vchan->vc.desc_free = sun4i_dma_free_contract;
> +		vchan_init(&vchan->vc, &priv->slave);
> +	}
> +
> +	ret = clk_prepare_enable(priv->clk);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Couldn't enable the clock\n");
> +		return ret;
> +	}
> +
> +	ret = devm_request_threaded_irq(&pdev->dev, priv->irq,
> +					sun4i_dma_interrupt,
> +					sun4i_dma_submit_work,
> +					0, dev_name(&pdev->dev), priv);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Cannot request IRQ\n");
> +		goto err_clk_disable;
> +	}
> +
> +	ret = dma_async_device_register(&priv->slave);
> +	if (ret) {
> +		dev_warn(&pdev->dev, "Failed to register DMA engine device\n");
> +		goto err_clk_disable;
> +	}
> +
> +	ret = of_dma_controller_register(pdev->dev.of_node, sun4i_dma_of_xlate,
> +					 priv);
> +	if (ret) {
> +		dev_err(&pdev->dev, "of_dma_controller_register failed\n");
> +		goto err_dma_unregister;
> +	}
> +
> +	dev_dbg(&pdev->dev, "Successfully probed SUN4I_DMA\n");
> +
> +	return 0;
> +
> +err_dma_unregister:
> +	dma_async_device_unregister(&priv->slave);
> +err_clk_disable:
> +	clk_disable_unprepare(priv->clk);
> +	return ret;
> +}
> +
> +static int sun4i_dma_remove(struct platform_device *pdev)
> +{
> +	struct sun4i_dma_dev *priv = platform_get_drvdata(pdev);
> +
> +	/* Disable IRQ so no more work is scheduled */
> +	disable_irq(priv->irq);
> +
> +	of_dma_controller_free(pdev->dev.of_node);
> +	dma_async_device_unregister(&priv->slave);
> +
> +	clk_disable_unprepare(priv->clk);
> +
> +	return 0;
> +}
> +
> +static struct of_device_id sun4i_dma_match[] = {
> +	{ .compatible = "allwinner,sun4i-a10-dma" },
> +	{ /* sentinel */ },
> +};
> +
> +static struct platform_driver sun4i_dma_driver = {
> +	.probe	= sun4i_dma_probe,
> +	.remove	= sun4i_dma_remove,
> +	.driver	= {
> +		.name		= "sun4i-dma",
> +		.of_match_table	= sun4i_dma_match,
> +	},
> +};
> +
> +module_platform_driver(sun4i_dma_driver);
> +
> +MODULE_DESCRIPTION("Allwinner A10 Dedicated DMA Controller Driver");
> +MODULE_AUTHOR("Emilio L?pez <emilio@elopez.com.ar>");
> +MODULE_LICENSE("GPL");
> -- 
> 2.0.1

Thanks!

Maxime


-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20140717/aa0132aa/attachment-0001.sig>

^ permalink raw reply

* [PATCH 0/3] mailbox: Add APM X-Gene platform mailbox driver
From: Feng Kan @ 2014-07-17 20:57 UTC (permalink / raw)
  To: linux-arm-kernel

This is to add the APM X-Gene platform mailbox driver. The mailbox driver
is based off Jassi Brar's mailbox framework. This patch set is based off 
the following git.

https://github.com/sumananna/mailbox.git

Feng Kan (3):
  mailbox: add support for APM X-Gene platform mailbox driver
  Documentation: mailbox: Add APM X-Gene SlimPro mailbox dts
    documentation
  arm64: dts: mailbox device tree node for APM X-Gene platform.

 .../bindings/mailbox/xgene-slimpro-mailbox.txt     |  41 +++
 arch/arm64/boot/dts/apm-storm.dtsi                 |  14 +
 drivers/mailbox/Kconfig                            |  10 +
 drivers/mailbox/Makefile                           |   1 +
 drivers/mailbox/mailbox-xgene-slimpro.c            | 287 +++++++++++++++++++++
 5 files changed, 353 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mailbox/xgene-slimpro-mailbox.txt
 create mode 100644 drivers/mailbox/mailbox-xgene-slimpro.c

-- 
1.9.1

^ permalink raw reply

* [PATCH 1/3] mailbox: add support for APM X-Gene platform mailbox driver
From: Feng Kan @ 2014-07-17 20:57 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405630645-15337-1-git-send-email-fkan@apm.com>

Add support for APM X-Gene platform mailbox driver.

Signed-off-by: Feng Kan <fkan@apm.com>
---
 drivers/mailbox/Kconfig                 |  10 ++
 drivers/mailbox/Makefile                |   1 +
 drivers/mailbox/mailbox-xgene-slimpro.c | 287 ++++++++++++++++++++++++++++++++
 3 files changed, 298 insertions(+)
 create mode 100644 drivers/mailbox/mailbox-xgene-slimpro.c

diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index c8b5c13..9df8d51 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -50,4 +50,14 @@ config OMAP_MBOX_KFIFO_SIZE
 	  Specify the default size of mailbox's kfifo buffers (bytes).
 	  This can also be changed at runtime (via the mbox_kfifo_size
 	  module parameter).
+
+config XGENE_SLIMPRO_MBOX
+	tristate "APM SoC X-Gene SLIMpro Mailbox Controller"
+	depends on ARCH_XGENE
+	help
+	  An implementation of the APM X-Gene Interprocessor Communication
+	  Mailbox (IPCM) between the ARM 64-bit cores and SLIMpro controller.
+	  It is used to send short messages between ARM64-bit cores and
+	  the SLIMpro Management Engine, primarily for PM. Say Y here if you
+	  want to use the APM X-Gene SLIMpro IPCM support.
 endif
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index e0facb3..4e53d8b 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_OMAP1_MBOX)	+= mailbox_omap1.o
 mailbox_omap1-objs		:= mailbox-omap1.o
 obj-$(CONFIG_OMAP2PLUS_MBOX)	+= mailbox_omap2.o
 mailbox_omap2-objs		:= mailbox-omap2.o
+obj-$(CONFIG_XGENE_SLIMPRO_MBOX) += mailbox-xgene-slimpro.o
diff --git a/drivers/mailbox/mailbox-xgene-slimpro.c b/drivers/mailbox/mailbox-xgene-slimpro.c
new file mode 100644
index 0000000..ae46cc3
--- /dev/null
+++ b/drivers/mailbox/mailbox-xgene-slimpro.c
@@ -0,0 +1,287 @@
+/*
+ * APM X-Gene SLIMpro MailBox Driver
+ *
+ * Copyright (c) 2014, Applied Micro Circuits Corporation
+ * Author: Feng Kan fkan at apm.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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/acpi.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/mailbox_controller.h>
+
+#define MBOX_CON_NAME			"slimpro-mbox"
+#define MBOX_REG_SET_OFFSET		0x1000
+#define MBOX_CNT			8
+#define MBOX_STATUS_AVAIL_MASK		0x00010000
+#define MBOX_STATUS_ACK_MASK		0x00000001
+
+/* Configuration and Status Registers */
+struct slimpro_mbox_reg {
+	u32 in;
+	u32 din0;
+	u32 din1;
+	u32 rsvd1;
+	u32 out;
+	u32 dout0;
+	u32 dout1;
+	u32 rsvd2;
+	u32 status;
+	u32 statusmask;
+};
+
+struct slimpro_mbox_chan {
+	struct device *dev;
+	struct mbox_chan *chan;
+	struct slimpro_mbox_reg __iomem *reg;
+	int id;
+	int irq;
+	u32 rx_msg[3];
+};
+
+struct slimpro_mbox {
+	struct mbox_controller mb_ctrl;
+	struct slimpro_mbox_chan mc[MBOX_CNT];
+	struct mbox_chan chans[MBOX_CNT];
+};
+
+static struct slimpro_mbox_chan *to_slimpro_mbox_chan(struct mbox_chan *chan)
+{
+	if (!chan || !chan->con_priv)
+		return NULL;
+
+	return (struct slimpro_mbox_chan *)chan->con_priv;
+}
+
+static void mb_chan_send_msg(struct slimpro_mbox_chan *mb_chan, u32 *msg)
+{
+	writel(msg[1], &mb_chan->reg->dout0);
+	writel(msg[2], &mb_chan->reg->dout1);
+	writel(msg[0], &mb_chan->reg->out);
+}
+
+static void mb_chan_recv_msg(struct slimpro_mbox_chan *mb_chan)
+{
+	mb_chan->rx_msg[1] = readl(&mb_chan->reg->din0);
+	mb_chan->rx_msg[2] = readl(&mb_chan->reg->din1);
+	mb_chan->rx_msg[0] = readl(&mb_chan->reg->in);
+}
+
+static void mb_chan_enable_int(struct slimpro_mbox_chan *mb_chan, u32 mask)
+{
+	u32 val = readl(&mb_chan->reg->statusmask);
+
+	val &= ~mask;
+
+	writel(val, &mb_chan->reg->statusmask);
+}
+
+static void mb_chan_disable_int(struct slimpro_mbox_chan *mb_chan, u32 mask)
+{
+	u32 val = readl(&mb_chan->reg->statusmask);
+
+	val |= mask;
+
+	writel(val, &mb_chan->reg->statusmask);
+}
+
+static int mb_chan_status_ack(struct slimpro_mbox_chan *mb_chan)
+{
+	u32 val = readl(&mb_chan->reg->status);
+
+	if (val & MBOX_STATUS_ACK_MASK) {
+		writel(MBOX_STATUS_ACK_MASK, &mb_chan->reg->status);
+		return 1;
+	}
+	return 0;
+}
+
+static int mb_chan_status_avail(struct slimpro_mbox_chan *mb_chan)
+{
+	u32 val = readl(&mb_chan->reg->status);
+
+	if (val & MBOX_STATUS_AVAIL_MASK) {
+		mb_chan_recv_msg(mb_chan);
+		writel(MBOX_STATUS_AVAIL_MASK, &mb_chan->reg->status);
+		return 1;
+	}
+	return 0;
+}
+
+static irqreturn_t slimpro_mbox_irq(int irq, void *id)
+{
+	struct slimpro_mbox_chan *mb_chan = id;
+
+	if (mb_chan_status_ack(mb_chan))
+		mbox_chan_txdone(mb_chan->chan, 0);
+
+	if (mb_chan_status_avail(mb_chan)) {
+		mb_chan_recv_msg(mb_chan);
+		mbox_chan_received_data(mb_chan->chan, mb_chan->rx_msg);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int slimpro_mbox_send_data(struct mbox_chan *chan, void *msg)
+{
+	struct slimpro_mbox_chan *mb_chan = to_slimpro_mbox_chan(chan);
+
+	mb_chan_send_msg(mb_chan, msg);
+	return 0;
+}
+
+static int slimpro_mbox_startup(struct mbox_chan *chan)
+{
+	struct slimpro_mbox_chan *mb_chan = to_slimpro_mbox_chan(chan);
+	int rc;
+
+	rc = devm_request_irq(mb_chan->dev, mb_chan->irq, slimpro_mbox_irq, 0,
+			      MBOX_CON_NAME, mb_chan);
+	if (unlikely(rc)) {
+		dev_err(mb_chan->dev, "failed to register mailbox interrupt %d\n",
+			mb_chan->irq);
+		return rc;
+	}
+
+	/* Enable HW interrupt */
+	writel(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK,
+		&mb_chan->reg->status);
+	mb_chan_enable_int(mb_chan, MBOX_STATUS_ACK_MASK |
+					MBOX_STATUS_AVAIL_MASK);
+	return 0;
+}
+
+static void slimpro_mbox_shutdown(struct mbox_chan *chan)
+{
+	struct slimpro_mbox_chan *mb_chan = to_slimpro_mbox_chan(chan);
+
+	mb_chan_disable_int(mb_chan, MBOX_STATUS_ACK_MASK |
+				  MBOX_STATUS_AVAIL_MASK);
+	devm_free_irq(mb_chan->dev, mb_chan->irq, mb_chan);
+}
+
+static struct mbox_chan_ops slimpro_mbox_ops = {
+	.send_data = slimpro_mbox_send_data,
+	.startup = slimpro_mbox_startup,
+	.shutdown = slimpro_mbox_shutdown,
+};
+
+static int __init slimpro_mbox_probe(struct platform_device *pdev)
+{
+	struct slimpro_mbox *ctx;
+	struct resource *regs;
+	void __iomem *mb_base;
+	int rc;
+	int i;
+
+	ctx = devm_kzalloc(&pdev->dev, sizeof(struct slimpro_mbox), GFP_KERNEL);
+	if (IS_ERR(ctx))
+		return PTR_ERR(ctx);
+	platform_set_drvdata(pdev, ctx);
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mb_base = devm_ioremap_resource(&pdev->dev, regs);
+	if (IS_ERR(mb_base))
+		return PTR_ERR(mb_base);
+
+	/* Setup mailbox links */
+	for (i = 0; i < MBOX_CNT; i++) {
+		ctx->mc[i].irq = platform_get_irq(pdev, i);
+		if (ctx->mc[i].irq < 0) {
+			dev_err(&pdev->dev, "no IRQ at index %d\n",
+				ctx->mc[i].irq);
+			return -ENODEV;
+		}
+
+		ctx->mc[i].dev = &pdev->dev;
+		ctx->mc[i].reg = mb_base + i * MBOX_REG_SET_OFFSET;
+		ctx->mc[i].id = i;
+		ctx->mc[i].chan = &ctx->chans[i];
+		ctx->chans[i].con_priv = &ctx->mc[i];
+	}
+
+	/* Setup mailbox controller */
+	ctx->mb_ctrl.dev = &pdev->dev;
+	ctx->mb_ctrl.chans = ctx->chans;
+	ctx->mb_ctrl.txdone_irq = true;
+	ctx->mb_ctrl.ops = &slimpro_mbox_ops;
+	ctx->mb_ctrl.num_chans = MBOX_CNT;
+
+	rc = mbox_controller_register(&ctx->mb_ctrl);
+	if (rc) {
+		dev_err(&pdev->dev,
+			"APM X-Gene SLIMpro MailBox register failed:%d\n", rc);
+		return rc;
+	}
+
+	dev_info(&pdev->dev, "APM X-Gene SLIMpro MailBox registered\n");
+	return 0;
+}
+
+static int slimpro_mbox_remove(struct platform_device *pdev)
+{
+	struct slimpro_mbox *smb = platform_get_drvdata(pdev);
+
+	mbox_controller_unregister(&smb->mb_ctrl);
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id slimpro_of_match[] = {
+	{.compatible = "apm,xgene-slimpro-mbox" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, slimpro_of_match);
+#endif
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id slimpro_acpi_ids[] = {
+	{"APMC0D01", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, slimpro_acpi_ids);
+#endif
+
+static struct platform_driver slimpro_mbox_driver = {
+	.probe	= slimpro_mbox_probe,
+	.remove = slimpro_mbox_remove,
+	.driver	= {
+		.name = "xgene-slimpro-mbox",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(slimpro_of_match),
+		.acpi_match_table = ACPI_PTR(slimpro_acpi_ids)
+	},
+};
+
+static int __init slimpro_mbox_init(void)
+{
+	return platform_driver_register(&slimpro_mbox_driver);
+}
+
+static void __exit slimpro_mbox_exit(void)
+{
+}
+
+subsys_initcall(slimpro_mbox_init);
+module_exit(slimpro_mbox_exit);
+
+MODULE_DESCRIPTION("APM X-Gene SLIMpro Mailbox Driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1

^ permalink raw reply related

* [PATCH 2/3] Documentation: mailbox: Add APM X-Gene SlimPro mailbox dts documentation
From: Feng Kan @ 2014-07-17 20:57 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405630645-15337-1-git-send-email-fkan@apm.com>

This adds the APM X-Gene Slimpro mailbox device tree node documentation.

Signed-off-by: Feng Kan <fkan@apm.com>
---
 .../bindings/mailbox/xgene-slimpro-mailbox.txt     | 41 ++++++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mailbox/xgene-slimpro-mailbox.txt

diff --git a/Documentation/devicetree/bindings/mailbox/xgene-slimpro-mailbox.txt b/Documentation/devicetree/bindings/mailbox/xgene-slimpro-mailbox.txt
new file mode 100644
index 0000000..43e14393
--- /dev/null
+++ b/Documentation/devicetree/bindings/mailbox/xgene-slimpro-mailbox.txt
@@ -0,0 +1,41 @@
+The APM X-Gene SLIMpro mailbox driver is used to communicate messages between
+the ARM64 processors and the Cortex M3 (dubbed SLIMpro). It uses a simple
+interrupt based door bell mechanism and can exchange simple messages using the
+internal registers.
+
+There are total of 8 interrupts in this mailbox. Each used for an individual
+door bell (or mailbox channel).
+
+Required properties:
+- compatible:	Should be as "apm, xgene-slimpro-mbox".
+
+- reg:		Contain the mailbox register address range.
+
+- interrupts:	Contain interrupt information for the mailbox device.
+
+- #mbox-cells:	Specify the number of parameters used by the mailbox client.
+		Currently only one to specify the mailbox channel number.
+
+Example:
+
+Mailbox Node:
+		mailbox: slimpro-mailbox at 10540000 {
+			compatible = "apm,xgene-slimpro-mbox";
+			reg = <0x0 0x10540000 0x0 0xa000>;
+			#mbox-cells = <1>;
+			interrupts =  	<0x0 0x0 0x4>,
+					<0x0 0x1 0x4>,
+					<0x0 0x2 0x4>,
+					<0x0 0x3 0x4>,
+					<0x0 0x4 0x4>,
+					<0x0 0x5 0x4>,
+					<0x0 0x6 0x4>,
+					<0x0 0x7 0x4>;
+		};
+
+Client Node:
+		i2cslimpro {
+			compatible = "apm,xgene-slimpro-i2c";
+			mbox = <&mailbox 0>;
+			mbox-names = "i2c-slimpro";
+		};
-- 
1.9.1

^ permalink raw reply related

* [PATCH 3/3] arm64: dts: mailbox device tree node for APM X-Gene platform.
From: Feng Kan @ 2014-07-17 20:57 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1405630645-15337-1-git-send-email-fkan@apm.com>

Mailbox device tree node for APM X-Gene platform.

Signed-off-by: Feng Kan <fkan@apm.com>
---
 arch/arm64/boot/dts/apm-storm.dtsi | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/arch/arm64/boot/dts/apm-storm.dtsi b/arch/arm64/boot/dts/apm-storm.dtsi
index 40aa96c..93bcb4e 100644
--- a/arch/arm64/boot/dts/apm-storm.dtsi
+++ b/arch/arm64/boot/dts/apm-storm.dtsi
@@ -272,6 +272,20 @@
 			};
 		};
 
+		mailbox: slimpro-mailbox at 10540000 {
+			compatible = "apm,xgene-slimpro-mbox";
+			reg = <0x0 0x10540000 0x0 0xa000>;
+			#mbox-cells = <1>;
+			interrupts =  	<0x0 0x0 0x4>,
+					<0x0 0x1 0x4>,
+					<0x0 0x2 0x4>,
+					<0x0 0x3 0x4>,
+					<0x0 0x4 0x4>,
+					<0x0 0x5 0x4>,
+					<0x0 0x6 0x4>,
+					<0x0 0x7 0x4>;
+		};
+
 		serial0: serial at 1c020000 {
 			status = "disabled";
 			device_type = "serial";
-- 
1.9.1

^ permalink raw reply related

* [PATCH v10 0/11] seccomp: add thread sync ability
From: Andy Lutomirski @ 2014-07-17 21:39 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <53C48986.5010109@oracle.com>

On Mon, Jul 14, 2014 at 6:53 PM, James Morris <james.l.morris@oracle.com> wrote:
> On 07/15/2014 04:59 AM, Kees Cook wrote:
>>
>> Hi James,
>>
>> Is this series something you would carry in the security-next tree?
>> That has traditionally been where seccomp features have landed in the
>> past.
>>
>> -Kees
>>
>>
>> On Fri, Jul 11, 2014 at 10:55 AM, Kees Cook <keescook@chromium.org> wrote:
>>>
>>> On Fri, Jul 11, 2014 at 9:49 AM, Oleg Nesterov <oleg@redhat.com> wrote:
>>>>
>>>> On 07/10, Kees Cook wrote:
>>>>>
>>>>>
>>>>> This adds the ability for threads to request seccomp filter
>>>>> synchronization across their thread group (at filter attach time).
>>>>> For example, for Chrome to make sure graphic driver threads are fully
>>>>> confined after seccomp filters have been attached.
>>>>>
>>>>> To support this, locking on seccomp changes via thread-group-shared
>>>>> sighand lock is introduced, along with refactoring of no_new_privs.
>>>>> Races
>>>>> with thread creation are handled via delayed duplication of the seccomp
>>>>> task struct field and cred_guard_mutex.
>>>>>
>>>>> This includes a new syscall (instead of adding a new prctl option),
>>>>> as suggested by Andy Lutomirski and Michael Kerrisk.
>>>>
>>>>
>>>> I do not not see any problems in this version,
>>>
>>>
>>> Awesome! Thank you for all the reviews. :) If Andy and Michael are
>>> happy with this too, I think this is in good shape. \o/
>>>
>>> -Kees
>>>
>>>>
>>>> Reviewed-by: Oleg Nesterov <oleg@redhat.com>
>>>>
>>>
>>>
>>>
>>> --
>>> Kees Cook
>>> Chrome OS Security
>>
>>
>>
>>
>
> Yep, certainly.
>

Any ETA?  I'm currently blocking on having stable commit hashes for these.

If you're planning on pulling from Kees' tree instead of importing the
patches, I can work with that, too.

Thanks,
Andy

^ permalink raw reply

* [PATCH v2 1/8] dma: sun4i: Add support for the DMA engine on sun[457]i SoCs
From: Emilio López @ 2014-07-17 21:45 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20140717205639.GH20328@lukather>

Hi Maxime,

El 17/07/14 17:56, Maxime Ripard escribi?:
> On Sun, Jul 06, 2014 at 01:05:08AM -0300, Emilio L?pez wrote:
>> This patch adds support for the DMA engine present on Allwinner A10,
>> A13, A10S and A20 SoCs. This engine has two kinds of channels: normal
>> and dedicated. The main difference is in the mode of operation;
>> while a single normal channel may be operating at any given time,
>> dedicated channels may operate simultaneously provided there is no
>> overlap of source or destination.
>>
>> Hardware documentation can be found on A10 User Manual (section 12), A13
>> User Manual (section 14) and A20 User Manual (section 1.12)
>>
>> Signed-off-by: Emilio L?pez <emilio@elopez.com.ar>
>> ---
>>(...)
>>
>> +config SUN4I_DMA
>> +	tristate "Allwinner A10/A10S/A13/A20 DMA support"
>
> I'm not that fond of having an exhaustive list here. If some other SoC
> we didn't thought of or get a new SoC that uses this controller, this
> list won't be exhaustive anymore, which is even worse.
>
> Just mention the A10.

Ok

>> +	depends on (MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || (COMPILE_TEST && OF && ARM))
>
> This pretty much defeats the purpose of COMPILE_TEST

QCOM_BAM_DMA does it that way; it's better to get some coverage than 
none I guess?

>
>> +	select DMA_ENGINE
>> +	select DMA_OF
>> +	select DMA_VIRTUAL_CHANNELS
>
> I guess you could default to y for the SoCs where it's relevant.

Sounds good.

>
>> +	help
>> +	  Enable support for the DMA controller present in the sun4i,
>> +	  sun5i and sun7i Allwinner ARM SoCs.
>> +
(...)
>> +
>> +/** Normal DMA register values **/
>> +
>> +/* Normal DMA source/destination data request type values */
>> +#define NDMA_DRQ_TYPE_SDRAM			0x16
>> +#define NDMA_DRQ_TYPE_LIMIT			(0x1F + 1)
>
> Hmmm, I'm unsure what this is about... What is it supposed to do, and
> how is it different from BIT(5) ?

if (val < NDMA_DRQ_TYPE_LIMIT)
	/* valid value */
else
	/* invalid value */

0x1F is the last valid value

(...)
>> +
>> +static void set_pchan_interrupt(struct sun4i_dma_dev *priv,
>> +				struct sun4i_dma_pchan *pchan,
>> +				int half, int end)
>> +{
>> +	u32 reg = readl_relaxed(priv->base + DMA_IRQ_ENABLE_REG);
>> +	int pchan_number = pchan - priv->pchans;
>> +
>> +	if (half)
>> +		reg |= BIT(pchan_number * 2);
>> +	else
>> +		reg &= ~BIT(pchan_number * 2);
>> +
>> +	if (end)
>> +		reg |= BIT(pchan_number * 2 + 1);
>> +	else
>> +		reg &= ~BIT(pchan_number * 2 + 1);
>> +
>> +	writel_relaxed(reg, priv->base + DMA_IRQ_ENABLE_REG);
>
> I don't see any interrupts here.

Hm?

> Is this suppose to be called with a
> lock taken? If so, it should be mentionned in some comment.

Good point, this should probably take a lock, I'll get it fixed.

(...)
>> +{
>> +	struct sun4i_dma_contract *contract = to_sun4i_dma_contract(vd);
>> +	struct sun4i_dma_promise *promise;
>> +
>> +	/* Free all the demands and completed demands */
>> +	list_for_each_entry(promise, &contract->demands, list) {
>> +		kfree(promise);
>> +	}
>> +
>> +	list_for_each_entry(promise, &contract->completed_demands, list) {
>> +		kfree(promise);
>> +	}
>>
>
> Those brackets are useless.

Indeed, dropped.

>> +	for_each_sg(sgl, sg, sg_len, i) {
>> +		/* Figure out addresses */
>> +		if (dir == DMA_MEM_TO_DEV) {
>> +			srcaddr = sg_dma_address(sg);
>> +			dstaddr = sconfig->dst_addr;
>> +		} else {
>> +			srcaddr = sconfig->src_addr;
>> +			dstaddr = sg_dma_address(sg);
>> +		}
>> +
>> +		/* TODO: should this be configurable? */
>> +		para = DDMA_MAGIC_SPI_PARAMETERS;
>
> What is it? Is it supposed to change from one client device to
> another?

These are the magic DMA engine timings that keep SPI going. I haven't 
seen any interface on DMAEngine to configure timings, and so far they 
seem to work for everything we support, so I've kept them here. I don't 
know if other devices need different timings because, as usual, we only 
have the "para" bitfield meanings, but no comment on what the values 
should be when doing a certain operation :|

>> +
>> +		/* And make a suitable promise */
>> +		if (vchan->is_dedicated)
>> +			promise = generate_ddma_promise(chan, srcaddr, dstaddr,
>> +							sg_dma_len(sg), sconfig);
>> +		else
>> +			promise = generate_ndma_promise(chan, srcaddr, dstaddr,
>> +							sg_dma_len(sg), sconfig);
>> +
>> +		if (!promise)
>> +			return NULL; /* TODO */
>
> TODO what?

/* TODO: properly kfree the promises generated in the loop */

(...)
>> +static enum dma_status sun4i_dma_tx_status(struct dma_chan *chan,
>> +					   dma_cookie_t cookie,
>> +					   struct dma_tx_state *state)
>> +{
>> +	struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
>> +	struct sun4i_dma_pchan *pchan = vchan->pchan;
>> +	struct sun4i_dma_contract *contract;
>> +	struct sun4i_dma_promise *promise;
>> +	struct virt_dma_desc *vd;
>> +	unsigned long flags;
>> +	enum dma_status ret;
>> +	size_t bytes = 0;
>> +
>> +	ret = dma_cookie_status(chan, cookie, state);
>> +	if (ret == DMA_COMPLETE)
>> +		return ret;
>> +
>> +	spin_lock_irqsave(&vchan->vc.lock, flags);
>> +	vd = vchan_find_desc(&vchan->vc, cookie);
>> +	if (!vd) /* TODO */
>
> TODO what?
>

/* TODO: remove the TODO */

>> +		goto exit;
>> +	contract = to_sun4i_dma_contract(vd);
>> +
>> +	list_for_each_entry(promise, &contract->demands, list) {
>> +		bytes += promise->len;
>> +	}
>
> Useless brackets

Dropped

Thanks for taking the time to review this!

Cheers,

Emilio

^ permalink raw reply


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