2nd stage of generating device tree from MRW.

Adds in UART, SPI, and LED nodes.

Change-Id: I8047c2d81e09151a3941c58f68fd30b4f1e2fa36
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/gen_devtree.pl b/gen_devtree.pl
index 9a564df..39040ba 100755
--- a/gen_devtree.pl
+++ b/gen_devtree.pl
@@ -44,16 +44,20 @@
 printRootNodeStart($f);
 
 printPropertyList($f, 1, "model", getSystemBMCModel());
-
 printPropertyList($f, 1, "compatible", getBMCCompatibles());
+
 printNode($f, 1, "chosen", getChosen());
 printNode($f, 1, "memory", getMemory($g_bmc));
 
-#TODO: LEDs, UART, I2C, aliases, pinctlr
+printNodes($f, 1, getSpiFlashNodes());
+
+printNode($f, 1, "leds", getLEDNode());
+
 printRootNodeEnd($f, 0);
 
+#TODO: I2C, aliases, pinctlr
 printNodes($f, 0, getMacNodes());
-
+printNodes($f, 0, getUARTNodes());
 printNodes($f, 0, getVuartNodes());
 
 close $f;
@@ -62,6 +66,11 @@
 
 
 #Return a hash that represents the 'chosen' node
+#Will look like:
+#   chosen {
+#      stdout-path = ...
+#      bootargs = ...
+#   }
 sub getChosen()
 {
     my $bmcStdOut = $g_targetObj->getAttributeField($g_bmc, "BMC_DT_CHOSEN",
@@ -75,50 +84,383 @@
 }
 
 
-#Returns a list of hashes that represent the MAC (ethernet) nodes on the BMC
-sub getMacNodes()
+#Gets the nodes that represents the BMC's SPI flash chips.  They're based
+#on information from the spi-master-unit end of the connection, with
+#a subnode of information from the destination chip.
+#On ASPEED chips, they're nested under the ahb node (Advanced
+#High-performance Bus).
+#Will look like:
+#   ahb {
+#     fmc@... {
+#       reg = ...
+#       #address-cells = ...
+#       #size-cells = ...
+#       #compatible = ...
+#
+#       flash@... {
+#          reg = ...
+#          compatible = ...
+#          label = ...
+# #include ...
+#       }
+#     }
+#     spi@... {
+#     ...
+#     }
+#   }
+sub getSpiFlashNodes()
 {
-    my @nodes;
-    my $children = $g_targetObj->getTargetChildren($g_bmc);
+    my %parentNode, my %node, my @nodes;
+    my $lastParentNodeName = "default";
+    my $parentNodeName = "ahb";
 
-    #The next version of this will look for ethernet connections in the
-    #MRW instead of just the units...
-    foreach my $c (@$children) {
+    my $connections = findConnections($g_bmc, "SPI", "FLASH");
+    if ($connections eq "") {
+        print "WARNING:  No SPI flashes found connected to the BMC\n";
+        return @nodes;
+    }
 
-        if ($g_targetObj->getTargetType($c) eq "unit-ethernet-master") {
+    foreach my $spi (@{$connections->{CONN}}) {
 
-            if ($g_targetObj->getAttribute($c, "UNIT_ENABLED") == 1) {
-                my %node;
-                my $num = $g_targetObj->getAttribute($c, "CHIP_UNIT");
-                my $ncsi = $g_targetObj->getAttribute($c, "NCSI_MODE");
-                my $hwChecksum = $g_targetObj->getAttribute($c,
-                                                          "USE_HW_CHECKSUM");
+        my %unitNode; #Node for the SPI master unit
+        my %flashNode; #subnode for the flash chip itself
+        my $flashNodeName = "flash";
+        my $nodeLabel = "";
+        my @addresses;
 
-                my $name = "mac$num";
-                $node{$name}{status} = "okay";
-                if ($ncsi == 1) {
-                    $node{$name}{"use-ncsi"} = STANDALONE_PROPERTY;
-                }
-                if ($hwChecksum == 0) {
-                    $node{$name}{"no-hw-checksum"} = STANDALONE_PROPERTY;
-                }
 
-                push @nodes, { %node };
+        #Adds a comment into the output file about the MRW connection
+        #that makes up this node.  Not that {SOURCE} always represents
+        #the master unit, and DEST_PARENT represents the destination
+        #chip.  The destination unit {DEST} isn't usually that interesting.
+        $unitNode{COMMENT} = "$spi->{SOURCE} ->\n$spi->{DEST_PARENT}";
+
+        #These flashes are nested in the 'ahb' (an internal chip bus)
+        #node in ASPEED chips.  Get the name of it here. Will default
+        #to 'ahb' if not set.
+        if (!$g_targetObj->isBadAttribute($spi->{SOURCE},
+                                        "INTERNAL_BUS", "NA")) {
+            $parentNodeName = $g_targetObj->getAttribute($spi->{SOURCE},
+                                                       "INTERNAL_BUS");
+            #Not going to support this unless we have to
+            if ($parentNodeName != $lastParentNodeName) {
+                die "ERROR: SPI master unit $spi->{SOURCE} has a " .
+                    "different internal bus name $parentNodeName than " .
+                    "previous name $lastParentNodeName\n";
+            }
+            else {
+                $lastParentNodeName = $parentNodeName;
             }
         }
+        else {
+            print "WARNING: No INTERNAL_BUS attribute value found for " .
+                  "SPI flash unit $spi->{SOURCE}. Using '$parentNodeName'\n";
+        }
+
+        #The reg base and size of the unit will be added into
+        #the reg property
+        my $regBase = $g_targetObj->getAttribute($spi->{SOURCE},
+                                               "BMC_DT_REG_BASE");
+        my $regSize = $g_targetObj->getAttribute($spi->{SOURCE},
+                                               "BMC_DT_REG_SIZE");
+
+        #There is also another memory range that goes into reg
+        my %sourceRegHash = getMemory($spi->{SOURCE});
+
+        #Insert the regBase and regSize to the memory < ... > property
+        $unitNode{reg} = "< $regBase $regSize " . substr($sourceRegHash{reg}, 2);
+
+        #usually, this will be something like 'smc' or 'spi'
+        my $nodeName = "spi";
+        if (!$g_targetObj->isBadAttribute($spi->{SOURCE},
+                                        "BMC_DT_NODE_NAME")) {
+            $nodeName = $g_targetObj->getAttribute($spi->{SOURCE},
+                                                 "BMC_DT_NODE_NAME");
+        }
+        else {
+            print "WARNING: No BMC_DT_NODE_NAME attribute value found for " .
+                  "SPI flash unit $spi->{SOURCE}. Using 'spi'\n";
+        }
+
+        #now turn it into something like fmc@...
+        $nodeName .= "@".$regBase;
+
+        if (!$g_targetObj->isBadAttribute($spi->{SOURCE},
+                                        "BMC_DT_COMPATIBLE")) {
+            $unitNode{compatible} = $g_targetObj->
+                    getAttribute($spi->{SOURCE}, "BMC_DT_COMPATIBLE");
+        }
+        else {
+            print "WARNING: No BMC_DT_COMPATIBLE attribute found for SPI " .
+                  "flash unit $spi->{SOURCE}\n";
+        }
+
+        #The flash chip has its one reg property as well
+        if (!$g_targetObj->isBadAttribute($spi->{DEST_PARENT},
+                                        "BMC_DT_REG_PROPERTY")) {
+            $flashNode{reg} = $g_targetObj->getAttribute($spi->{DEST_PARENT},
+                                                       "BMC_DT_REG_PROPERTY");
+            $flashNode{reg} = "<" . $flashNode{reg} . ">";
+        }
+        else {
+            print "WARNING: No BMC_REG_PROPERTY attribute found for SPI " .
+                  "flash $spi->{DEST_PARENT}.  Using <0>.\n";
+            $flashNode{reg} = "<0>";
+        }
+
+        if (!$g_targetObj->isBadAttribute($spi->{DEST_PARENT},
+                                        "BMC_DT_COMPATIBLE")) {
+            $flashNode{compatible} = $g_targetObj->
+                    getAttribute($spi->{DEST_PARENT}, "BMC_DT_COMPATIBLE");
+        }
+        else {
+            print "WARNING: No BMC_DT_COMPATIBLE attribute found for SPI " .
+                  "flash $spi->{DEST_PARENT}\n";
+        }
+
+        if (!$g_targetObj->isBadAttribute($spi->{DEST_PARENT},
+                                        "BMC_DT_LABEL_PROPERTY")) {
+            $flashNode{label} = $g_targetObj->
+                    getAttribute($spi->{DEST_PARENT}, "BMC_DT_LABEL_PROPERTY");
+        }
+
+        #Some flash chips have a .dtsi include to pull in more properties.
+        #Future - contents of the includes could be pulled into the MRW
+        #as new attributes.
+        if (!$g_targetObj->isBadAttribute($spi->{DEST_PARENT},
+                                        "BMC_DT_INCLUDES")) {
+            my $incs = $g_targetObj->
+                    getAttribute($spi->{DEST_PARENT}, "BMC_DT_INCLUDES");
+            #first remove the spaces and NAs
+            $incs =~ s/\s+//g;
+            $incs =~ s/NA,*//g;
+            $flashNode{DTSI_INCLUDE} = $incs;
+        }
+
+        #the flash subnode name also has its reg[0] appended
+        #like flash@...
+        @addresses = split(' ', $flashNode{reg});
+        $addresses[0] =~ s/<//;
+        $addresses[0] =~ s/>//;
+        $flashNodeName .= "@" . $addresses[0];
+        $unitNode{$flashNodeName} = { %flashNode };
+
+        #For now, just support a chip with 1 reg value
+        if (scalar @addresses == 1) {
+            $unitNode{'#address-cells'} = "<1>";
+            $unitNode{'#size-cells'} = "<0>";
+        }
+        else {
+            die "ERROR:  Unsupported number of <reg> entries " .
+                "in flash node $flashNodeName for SPI flash " .
+                "$spi->{DEST_PARENT}.  Only 1 entry supported.\n";
+        }
+
+        #This node will end up being in an array on the parent node
+        my %node;
+        $node{$nodeName} = { %unitNode };
+        push @nodes, { %node };
     }
+
+    $parentNode{$parentNodeName}{nodes} = [ @nodes ];
+
+    #There is always just one in the array
+    my @finalNodes;
+    push @finalNodes, { %parentNode };
+    return @finalNodes;
+}
+
+
+#Returns a hash that represents the leds node by finding all of the
+#GPIO connections to LEDs.
+#Node will look like:
+#   leds {
+#       <ledname> {
+#          gpios =  &gpio ASPEED_GPIO(x, y) GPIO_ACTIVE_xxx>
+#       };
+#       <another ledname> {
+#       ...
+#   }
+sub getLEDNode()
+{
+    my %leds;
+
+    $leds{compatible} = "gpio-led";
+
+    my $connections = findConnections($g_bmc, "GPIO", "LED");
+
+    if ($connections eq "") {
+        print "WARNING:  No LEDs found connected to the BMC\n";
+        return %leds;
+    }
+
+    foreach my $gpio (@{$connections->{CONN}}) {
+        my %ledNode;
+
+        $ledNode{COMMENT} = "$gpio->{SOURCE} ->\n$gpio->{DEST_PARENT}";
+
+        #The node name will be the simplified LED name
+        my $name = $gpio->{DEST_PARENT};
+        $name =~ s/(-\d+$)//; #remove trailing position
+        $name =~ s/.*\///;    #remove the front of the path
+
+        #For now only supports ASPEED.
+        if (uc($g_bmcMfgr) ne "ASPEED") {
+            die "ERROR:  Unsupported BMC manufacturer $g_bmcMfgr\n";
+        }
+        my $num = $g_targetObj->getAttribute($gpio->{SOURCE}, "PIN_NUM");
+        my $macro = getAspeedGpioMacro($num);
+
+        #If it's active high or low
+        my $state = $g_targetObj->getAttribute($gpio->{DEST_PARENT}, "ON_STATE");
+        my $activeString = getGpioActiveString($state);
+
+        $ledNode{gpios} = "<&gpio $macro $activeString>";
+
+        $leds{$name} = { %ledNode };
+    }
+
+    return %leds;
+}
+
+
+#Returns a either GPIO_ACTIVE_HIGH or GPIO_ACTIVE_LOW
+#  $val = either a 1 or a 0 for active high or low
+sub getGpioActiveString() {
+    my $val = shift;
+
+    if ($val == 0) {
+       return "GPIO_ACTIVE_LOW";
+    }
+
+    return "GPIO_ACTIVE_HIGH";
+}
+
+
+#Turns a GPIO number into something like ASPEED_GPIO(A, 0) for the
+#ASPEED GPIO numbering scheme A[0-7] -> Z[0-7] and then starts at
+#AA[0-7] after that.
+#  $num = the GPIO number
+sub getAspeedGpioMacro() {
+    my $num = shift;
+    my $char;
+    my $offset = $num % 8;
+    my $block = int($num / 8);
+
+    #If past Z, wraps to AA, AB, etc
+    if ((ord('A') + $block) > ord('Z')) {
+        #how far past Z?
+        $char = $block - (ord('Z') - ord('A'));
+
+        #Don't let it wrap twice
+        if ($char > (ord('Z') - ord('A') + 1)) {
+            die "ERROR: Invalid PIN_NUM value $num found for GPIO\n";
+        }
+
+        #start back at 'A' again, and convert to a character
+        $char = chr($char + ord('A') - 1);
+
+        #Add in a bonus 'A', to get something like AB
+        $char = "A".$char;
+    }
+    else {
+        $char = ord('A') + $block;
+        $char = chr($char);
+    }
+
+    return "ASPEED_GPIO($char, $offset)";
+}
+
+
+#Returns a list of hashes that represent the UART nodes on the BMC by
+#finding the UART connections.
+#Nodes will look like:
+#  &uartX {
+#     status = "okay"
+#  }
+sub getUARTNodes()
+{
+    my @nodes;
+
+    my $connections = findConnections($g_bmc, "UART");
+
+    if ($connections eq "") {
+        print "WARNING:  No UART buses found connected to the BMC\n";
+        return @nodes;
+    }
+
+    foreach my $uart (@{$connections->{CONN}}) {
+        my %node;
+
+        my $num = $g_targetObj->getAttribute($uart->{SOURCE}, "CHIP_UNIT");
+        my $name = "uart$num";
+
+        $node{$name}{status} = "okay";
+        $node{$name}{COMMENT} = "$uart->{SOURCE} ->\n$uart->{DEST_PARENT}";
+
+        push @nodes, { %node };
+    }
+
     return @nodes;
 }
 
 
-#Returns a last of hashes that represent the virtual UART nodes
+#Returns a list of hashes that represent the MAC (ethernet) nodes on the BMC
+#by finding the connections of type ETHERNET.
+#Nodes will look like:
+#  &macX {
+#    ...
+#  }
+sub getMacNodes()
+{
+    my @nodes;
+
+    my $connections = findConnections($g_bmc, "ETHERNET");
+
+    if ($connections eq "") {
+        print "WARNING:  No ethernet buses found connected to the BMC\n";
+        return @nodes;
+    }
+
+    foreach my $eth (@{$connections->{CONN}}) {
+        my %node;
+
+        my $num = $g_targetObj->getAttribute($eth->{SOURCE}, "CHIP_UNIT");
+        my $ncsi = $g_targetObj->getAttribute($eth->{SOURCE}, "NCSI_MODE");
+        my $hwChecksum = $g_targetObj->getAttribute($eth->{SOURCE},
+                                                  "USE_HW_CHECKSUM");
+
+        my $name = "mac$num";
+        $node{$name}{status} = "okay";
+
+        if ($ncsi == 1) {
+            $node{$name}{"use-ncsi"} = STANDALONE_PROPERTY;
+        }
+        if ($hwChecksum == 0) {
+            $node{$name}{"no-hw-checksum"} = STANDALONE_PROPERTY;
+        }
+
+        $node{$name}{COMMENT} = "$eth->{SOURCE} ->\n$eth->{DEST_PARENT}";
+
+        push @nodes, { %node };
+    }
+
+    return @nodes;
+}
+
+
+#Returns a list of hashes that represent the virtual UART nodes
+#Node will look like:
+#  &vuart {
+#   status = "okay"
+#  }
 sub getVuartNodes()
 {
     my @nodes;
     my %node;
 
     #For now, enable 1 node all the time.
-    #TODO if this needs to be fixed.
+    #TBD if this needs to be fixed
     $node{vuart}{status} = "okay";
 
     push @nodes, { %node };
@@ -136,7 +478,7 @@
     my $memory = $g_targetObj->getAttribute($target, "BMC_DT_MEMORY");
     my @mem = split(',', $memory);
     my %property;
-    my $val = "<";
+    my $val = "< ";
 
     #Encoded as 4 <base address>,<size> pairs of memory ranges
     #Unused ranges are all 0s.
@@ -152,7 +494,7 @@
     }
 
     $val =~ s/\s$//;
-    $val .= ">";
+    $val .= " >";
     $property{reg} = $val;
 
     return %property;
@@ -181,7 +523,7 @@
 #Returns a string for the system's BMC model property
 sub getSystemBMCModel()
 {
-    #<System> BMC
+    #'<System> BMC'
     my $sys = lc $g_systemName;
     $sys = uc(substr($sys, 0, 1)) . substr($sys, 1);
 
@@ -217,13 +559,13 @@
 #  %vals = The contents of the node, with the following options:
 #     if the key is:
 #     - 'DTSI_INCLUDE', then value gets turned into a #include
-#     - 'COMMENT', then value gets turned into a // comment (coming soon)
+#     - 'COMMENT', then value gets turned into a // comment
 #     - 'STANDALONE_PROPERTY' then value gets turned into:  value;
 #
 #     If the value is:
 #     - a hash - then that hash gets turned into a child node
 #       where the key is the name of the child node
-#     - an array of hashes indicates an array of nodes (coming soon)
+#     - an array of hashes indicates an array of child nodes
 sub printNode()
 {
     my ($f, $level, $name, %vals) = @_;
@@ -233,10 +575,21 @@
         $name = "&".$name;
     }
 
-    print $f "\n".indent($level) . "$name {\n";
+    print $f "\n";
+
+    if (exists $vals{COMMENT}) {
+        my @lines = split('\n', $vals{COMMENT});
+        foreach my $l (@lines) {
+            print $f indent($level) . "// $l\n";
+        }
+    }
+
+    print $f indent($level) . "$name {\n";
 
     foreach my $v (sort keys %vals) {
 
+        next if ($v eq "COMMENT");
+
         #A header file include, print it later
         if ($v eq DTSI_INCLUDE) {
             $include = $vals{$v};
@@ -245,6 +598,11 @@
         elsif (ref($vals{$v}) eq "HASH") {
             printNode($f, $level+1, $v, %{$vals{$v}});
         }
+        #An array of nested nodes
+        elsif (ref($vals{$v}) eq "ARRAY") {
+            my @array = @{$vals{$v}};
+            &printNodes($f, $level+1, @array);
+        }
         elsif ($vals{$v} ne STANDALONE_PROPERTY) {
             printProperty($f, $level+1, $v, $vals{$v});
         }
@@ -373,15 +731,14 @@
     my $target = shift;
     my @includes;
 
-
     if (!$g_targetObj->isBadAttribute($target, "BMC_DT_INCLUDES")) {
         my $attr = $g_targetObj->getAttribute($target, "BMC_DT_INCLUDES");
+        $attr =~ s/\s+//g; #remove whitespace
+        $attr =~ s/NA,*//g; #remove the NAs
         my @incs = split(',', $attr);
 
         foreach my $i (@incs) {
-            if ($i ne "NA") {
-                push @includes, $i
-            }
+            push @includes, $i
         }
     }
 
@@ -415,6 +772,64 @@
 }
 
 
+#Will look for all the connections of the specified type coming from
+#any sub target of the specified target, instead of just 1 level down
+#like the Targets inteface does.  Needed because sometimes we have
+#target->pingroup->sourceunit instead of just target->sourceunit
+#  $target = the target to find connections off of
+#  $bus = the bus type
+#  $partType = destination part type, leave off if a don't care
+sub findConnections() {
+    my ($target, $bus, $partType) = @_;
+    my %allConnections;
+    my $i = 0;
+
+    #get the ones from target->child
+    my $connections = $g_targetObj->findConnections($target, $bus, $partType);
+    if ($connections ne "") {
+        foreach my $c (@{$connections->{CONN}}) {
+            $allConnections{CONN}[$i] = { %{$c} };
+            $i++;
+        }
+    }
+
+    #get everything deeper
+    my @children = getAllTargetChildren($target);
+    foreach my $c (@children) {
+        my $connections = $g_targetObj->findConnections($c, $bus, $partType);
+        if ($connections ne "") {
+
+            foreach my $c (@{$connections->{CONN}}) {
+                $allConnections{CONN}[$i] = { %{$c} };
+                $i++;
+            }
+        }
+    }
+
+    return \%allConnections;
+}
+
+#Returns every sub target, not just the 1st level children.
+#  $target = the target to find the children of
+sub getAllTargetChildren()
+{
+    my $target = shift;
+    my @children;
+
+    my $targets = $g_targetObj->getTargetChildren($target);
+    if ($targets ne "") {
+
+        foreach my $t (@$targets) {
+            push @children, $t;
+            my @more = getAllTargetChildren($t);
+            push @children, @more;
+        }
+    }
+
+    return @children;
+}
+
+
 sub printUsage
 {
     print "gen_devtree.pl -x [XML filename] -o [output filename]\n";