dts: dtlib/edtlib: Add phandle and phandle+nums array types

Add two new type-checked property types 'phandles' and 'phandle-array'
to edtlib.

'phandles' is for pure lists of phandles, with no other data, like

    foo = < &bar &baz ... >

'phandle-array' is for lists of phandles and (possibly) numbers, like

    foo = < &bar 1 2 &baz 3 4 ... >

dt-schema also has the 'phandle-array' type.

Property.val (in edtlib) is set to an array of Device objects for the
'phandles' type.

For the 'phandle-array' type, no Property object is created. This type
is only used for type checking.

Also refactor how types that do not create a Property object
('phandle-array' and 'compound') are handled. Have _prop_val() return
None for them.

The new types are implemented with two new TYPE_PHANDLES and
TYPE_PHANDLES_AND_NUMS types at the dtlib level. There is also a new
Property.to_nodes() functions for fetching the Nodes for an array of
phandles, with type checking.

Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
diff --git a/scripts/dts/testdtlib.py b/scripts/dts/testdtlib.py
index 1fd2648..029b865 100755
--- a/scripts/dts/testdtlib.py
+++ b/scripts/dts/testdtlib.py
@@ -1451,13 +1451,17 @@
 	nums4 = < 1 2 >, < 3 >, < 4 >;
 	string = "foo";
 	strings = "foo", "bar";
-	phandle1 = < &node >;
-	phandle2 = < &{/node} >;
 	path1 = &node;
 	path2 = &{/node};
+	phandle1 = < &node >;
+	phandle2 = < &{/node} >;
+	phandles1 = < &node &node >;
+	phandles2 = < &node >, < &node >;
+	phandle-and-nums-1 = < &node 1 >;
+	phandle-and-nums-2 = < &node 1 2 &node 3 4 >;
+	phandle-and-nums-3 = < &node 1 2 >, < &node 3 4 >;
 	compound1 = < 1 >, [ 02 ];
 	compound2 = "foo", < >;
-	compound3 = < 1 &{/node} 2>;
 
 	node: node {
 	};
@@ -1479,11 +1483,15 @@
     verify_type("strings", dtlib.TYPE_STRINGS)
     verify_type("phandle1", dtlib.TYPE_PHANDLE)
     verify_type("phandle2", dtlib.TYPE_PHANDLE)
+    verify_type("phandles1", dtlib.TYPE_PHANDLES)
+    verify_type("phandles2", dtlib.TYPE_PHANDLES)
+    verify_type("phandle-and-nums-1", dtlib.TYPE_PHANDLES_AND_NUMS)
+    verify_type("phandle-and-nums-2", dtlib.TYPE_PHANDLES_AND_NUMS)
+    verify_type("phandle-and-nums-3", dtlib.TYPE_PHANDLES_AND_NUMS)
     verify_type("path1", dtlib.TYPE_PATH)
     verify_type("path2", dtlib.TYPE_PATH)
     verify_type("compound1", dtlib.TYPE_COMPOUND)
     verify_type("compound2", dtlib.TYPE_COMPOUND)
-    verify_type("compound3", dtlib.TYPE_COMPOUND)
 
     #
     # Test Property.to_{num,nums,string,strings,node}()
@@ -1511,6 +1519,8 @@
 	strings = "foo", "bar", "baz";
 	invalid_strings = "foo", "\xff", "bar";
 	ref = <&{/target}>;
+	refs = <&{/target} &{/target2}>;
+	refs2 = <&{/target}>, <&{/target2}>;
 	path = &{/target};
 	manualpath = "/target";
 	missingpath = "/missing";
@@ -1518,6 +1528,9 @@
 	target {
 		phandle = < 100 >;
 	};
+
+	target2 {
+	};
 };
 """)
 
@@ -1717,6 +1730,38 @@
     verify_to_node_error("u", "expected property 'u' on / in .tmp.dts to be assigned with 'u = < &foo >;', not 'u = < 0x1 >;'")
     verify_to_node_error("string", "expected property 'string' on / in .tmp.dts to be assigned with 'string = < &foo >;', not 'string = \"foo\\tbar baz\";'")
 
+    # Test Property.to_nodes()
+
+    def verify_to_nodes(prop, paths):
+        try:
+            actual = [node.path for node in dt.root.props[prop].to_nodes()]
+        except dtlib.DTError as e:
+            fail("failed to convert '{}' to nodes: {}".format(prop, e))
+
+        if actual != paths:
+            fail("expected {} to point to the paths {}, pointed to {}"
+                 .format(prop, paths, actual))
+
+    def verify_to_nodes_error(prop, msg):
+        prefix = "expected converting '{}' to a nodes to generate the error " \
+                 "'{}', generated".format(prop, msg)
+        try:
+            dt.root.props[prop].to_nodes()
+            fail(prefix + " no error")
+        except dtlib.DTError as e:
+            if str(e) != msg:
+                fail("{} the error '{}'".format(prefix, e))
+        except Exception as e:
+            fail("{} the non-DTError '{}'".format(prefix, e))
+
+    verify_to_nodes("zero", [])
+    verify_to_nodes("ref", ["/target"])
+    verify_to_nodes("refs", ["/target", "/target2"])
+    verify_to_nodes("refs2", ["/target", "/target2"])
+
+    verify_to_nodes_error("u", "expected property 'u' on / in .tmp.dts to be assigned with 'u = < &foo &bar ... >;', not 'u = < 0x1 >;'")
+    verify_to_nodes_error("string", "expected property 'string' on / in .tmp.dts to be assigned with 'string = < &foo &bar ... >;', not 'string = \"foo\\tbar baz\";'")
+
     # Test Property.to_path()
 
     def verify_to_path(prop, path):