dtoc: Process nodes to set up required properties

Add logic to assign property values to nodes as required by dtoc. The
references allow nodes to refer to each other in C code. The macros used
by dtoc are not yet defined in driver model. They will be added along
with the actual driver model implementation.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/tools/dtoc/dtb_platdata.py b/tools/dtoc/dtb_platdata.py
index 2ec22ed..ad71f70 100644
--- a/tools/dtoc/dtb_platdata.py
+++ b/tools/dtoc/dtb_platdata.py
@@ -647,6 +647,38 @@
             self._output_prop(node, node.props[pname])
         self.buf('};\n')
 
+    def process_nodes(self, need_drivers):
+        nodes_to_output = list(self._valid_nodes)
+
+        for node in nodes_to_output:
+            node.dev_ref = 'DM_DEVICE_REF(%s)' % node.var_name
+            driver = self._scan.get_driver(node.struct_name)
+            if not driver:
+                if not need_drivers:
+                    continue
+                raise ValueError("Cannot parse/find driver for '%s'" %
+                                 node.struct_name)
+            node.driver = driver
+            parent_driver = None
+            if node.parent in self._valid_nodes:
+                parent_driver = self._scan.get_driver(node.parent.struct_name)
+                if not parent_driver:
+                    if not need_drivers:
+                        continue
+                    raise ValueError(
+                        "Cannot parse/find parent driver '%s' for '%s'" %
+                        (node.parent.struct_name, node.struct_name))
+                node.parent_seq = len(node.parent.child_devs)
+                node.parent.child_devs.append(node)
+                node.parent.child_refs[node.parent_seq] = \
+                    '&%s->sibling_node' % node.dev_ref
+                node.parent_driver = parent_driver
+
+        for node in nodes_to_output:
+            ref = '&%s->child_head' % node.dev_ref
+            node.child_refs[-1] = ref
+            node.child_refs[len(node.child_devs)] = ref
+
     def output_node(self, node):
         """Output the C code for a node
 
@@ -731,6 +763,9 @@
     if not scan:
         scan = src_scan.Scanner(basedir, warning_disabled, drivers_additional)
         scan.scan_drivers()
+        do_process = True
+    else:
+        do_process = False
     plat = DtbPlatdata(scan, dtb_file, include_disabled)
     plat.scan_dtb()
     plat.scan_tree()
@@ -739,6 +774,8 @@
     plat.setup_output_dirs(output_dirs)
     plat.scan_structs()
     plat.scan_phandles()
+    if do_process:
+        plat.process_nodes(False)
 
     cmds = args[0].split(',')
     if 'all' in cmds:
diff --git a/tools/dtoc/src_scan.py b/tools/dtoc/src_scan.py
index bf3e5de..504dac0 100644
--- a/tools/dtoc/src_scan.py
+++ b/tools/dtoc/src_scan.py
@@ -188,6 +188,17 @@
         self._uclass = {}
         self._structs = {}
 
+    def get_driver(self, name):
+        """Get a driver given its name
+
+        Args:
+            name (str): Driver name
+
+        Returns:
+            Driver: Driver or None if not found
+        """
+        return self._drivers.get(name)
+
     def get_normalized_compat_name(self, node):
         """Get a node's normalized compat name
 
diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py
index 9049c28..3e98e36 100755
--- a/tools/dtoc/test_dtoc.py
+++ b/tools/dtoc/test_dtoc.py
@@ -953,3 +953,79 @@
         self.assertEqual(
             {'dt-structs-gen.h', 'source.dts', 'dt-plat.c', 'source.dtb'},
             leafs)
+
+    def setup_process_test(self):
+        """Set up a test of process_nodes()
+
+        This uses saved_scan but returns a deep copy of it, so it is safe to
+        modify it in these tests
+
+        Returns:
+            tuple:
+                DtbPlatdata: object to test
+                Scanner: scanner to use
+        """
+        dtb_file = get_dtb_file('dtoc_test_simple.dts')
+        output = tools.GetOutputFilename('output')
+
+        # Take a copy before messing with it
+        scan = copy.deepcopy(saved_scan)
+        plat = dtb_platdata.DtbPlatdata(scan, dtb_file, False)
+        plat.scan_dtb()
+        plat.scan_tree()
+        plat.prepare_nodes()
+        return plat, scan
+
+    def test_process_nodes(self):
+        """Test processing nodes to add various info"""
+        plat, scan = self.setup_process_test()
+        plat.process_nodes(True)
+
+        i2c_node = plat._fdt.GetNode('/i2c@0')
+        pmic_node = plat._fdt.GetNode('/i2c@0/pmic@9')
+        pmic = scan._drivers['sandbox_pmic']
+        i2c = scan._drivers['sandbox_i2c']
+        self.assertEqual('DM_DEVICE_REF(pmic_at_9)', pmic_node.dev_ref)
+        self.assertEqual(pmic, pmic_node.driver)
+        self.assertEqual(i2c_node, pmic_node.parent)
+        self.assertEqual(i2c, pmic_node.parent_driver)
+
+        # The pmic is the only child
+        self.assertEqual(pmic_node.parent_seq, 0)
+        self.assertEqual([pmic_node], i2c_node.child_devs)
+
+        # Start and end of the list should be the child_head
+        ref = '&DM_DEVICE_REF(i2c_at_0)->child_head'
+        self.assertEqual(
+            {-1: ref, 0: '&DM_DEVICE_REF(pmic_at_9)->sibling_node', 1: ref},
+            i2c_node.child_refs)
+
+    def test_process_nodes_bad_parent(self):
+        # Pretend that i2c has a parent (the pmic) and delete that driver
+        plat, scan = self.setup_process_test()
+
+        i2c_node = plat._fdt.GetNode('/i2c@0')
+        pmic_node = plat._fdt.GetNode('/i2c@0/pmic@9')
+        del scan._drivers['sandbox_pmic']
+        i2c_node.parent = pmic_node
+
+        # Process twice, the second time to generate an exception
+        plat.process_nodes(False)
+        with self.assertRaises(ValueError) as exc:
+            plat.process_nodes(True)
+        self.assertIn(
+            "Cannot parse/find parent driver 'sandbox_pmic' for 'sandbox_i2c",
+            str(exc.exception))
+
+    def test_process_nodes_bad_node(self):
+        plat, scan = self.setup_process_test()
+
+        # Now remove the pmic driver
+        del scan._drivers['sandbox_pmic']
+
+        # Process twice, the second time to generate an exception
+        plat.process_nodes(False)
+        with self.assertRaises(ValueError) as exc:
+            plat.process_nodes(True)
+        self.assertIn("Cannot parse/find driver for 'sandbox_pmic",
+                      str(exc.exception))