*      2 of the License, or (at your option) any later version.
  */
 
+#include <linux/cpu.h>
 #include <linux/types.h>
+#include <linux/delay.h>
 #include <linux/stat.h>
 #include <linux/device.h>
 #include <linux/init.h>
        struct vio_driver *viodrv = to_vio_driver(dev->driver);
        unsigned long flags;
        size_t size;
+       bool dma_capable = false;
+
+       /* A device requires entitlement if it has a DMA window property */
+       switch (viodev->family) {
+       case VDEVICE:
+               if (of_get_property(viodev->dev.of_node,
+                                       "ibm,my-dma-window", NULL))
+                       dma_capable = true;
+               break;
+       case PFO:
+               dma_capable = false;
+               break;
+       default:
+               dev_warn(dev, "unknown device family: %d\n", viodev->family);
+               BUG();
+               break;
+       }
 
-       /*
-        * Check to see that device has a DMA window and configure
-        * entitlement for the device.
-        */
-       if (of_get_property(viodev->dev.of_node,
-                           "ibm,my-dma-window", NULL)) {
+       /* Configure entitlement for the device. */
+       if (dma_capable) {
                /* Check that the driver is CMO enabled and get desired DMA */
                if (!viodrv->get_desired_dma) {
                        dev_err(dev, "%s: device driver does not support CMO\n",
 EXPORT_SYMBOL(vio_cmo_entitlement_update);
 EXPORT_SYMBOL(vio_cmo_set_dev_desired);
 
+
+/*
+ * Platform Facilities Option (PFO) support
+ */
+
+/**
+ * vio_h_cop_sync - Perform a synchronous PFO co-processor operation
+ *
+ * @vdev - Pointer to a struct vio_dev for device
+ * @op - Pointer to a struct vio_pfo_op for the operation parameters
+ *
+ * Calls the hypervisor to synchronously perform the PFO operation
+ * described in @op.  In the case of a busy response from the hypervisor,
+ * the operation will be re-submitted indefinitely unless a non-zero timeout
+ * is specified or an error occurs. The timeout places a limit on when to
+ * stop re-submitting a operation, the total time can be exceeded if an
+ * operation is in progress.
+ *
+ * If op->hcall_ret is not NULL, this will be set to the return from the
+ * last h_cop_op call or it will be 0 if an error not involving the h_call
+ * was encountered.
+ *
+ * Returns:
+ *     0 on success,
+ *     -EINVAL if the h_call fails due to an invalid parameter,
+ *     -E2BIG if the h_call can not be performed synchronously,
+ *     -EBUSY if a timeout is specified and has elapsed,
+ *     -EACCES if the memory area for data/status has been rescinded, or
+ *     -EPERM if a hardware fault has been indicated
+ */
+int vio_h_cop_sync(struct vio_dev *vdev, struct vio_pfo_op *op)
+{
+       struct device *dev = &vdev->dev;
+       unsigned long deadline = 0;
+       long hret = 0;
+       int ret = 0;
+
+       if (op->timeout)
+               deadline = jiffies + msecs_to_jiffies(op->timeout);
+
+       while (true) {
+               hret = plpar_hcall_norets(H_COP, op->flags,
+                               vdev->resource_id,
+                               op->in, op->inlen, op->out,
+                               op->outlen, op->csbcpb);
+
+               if (hret == H_SUCCESS ||
+                   (hret != H_NOT_ENOUGH_RESOURCES &&
+                    hret != H_BUSY && hret != H_RESOURCE) ||
+                   (op->timeout && time_after(deadline, jiffies)))
+                       break;
+
+               dev_dbg(dev, "%s: hcall ret(%ld), retrying.\n", __func__, hret);
+       }
+
+       switch (hret) {
+       case H_SUCCESS:
+               ret = 0;
+               break;
+       case H_OP_MODE:
+       case H_TOO_BIG:
+               ret = -E2BIG;
+               break;
+       case H_RESCINDED:
+               ret = -EACCES;
+               break;
+       case H_HARDWARE:
+               ret = -EPERM;
+               break;
+       case H_NOT_ENOUGH_RESOURCES:
+       case H_RESOURCE:
+       case H_BUSY:
+               ret = -EBUSY;
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       if (ret)
+               dev_dbg(dev, "%s: Sync h_cop_op failure (ret:%d) (hret:%ld)\n",
+                               __func__, ret, hret);
+
+       op->hcall_err = hret;
+       return ret;
+}
+EXPORT_SYMBOL(vio_h_cop_sync);
+
 static struct iommu_table *vio_build_iommu_table(struct vio_dev *dev)
 {
        const unsigned char *dma_window;
 struct vio_dev *vio_register_device_node(struct device_node *of_node)
 {
        struct vio_dev *viodev;
+       struct device_node *parent_node;
        const unsigned int *unit_address;
+       const unsigned int *pfo_resid = NULL;
+       enum vio_dev_family family;
+       const char *of_node_name = of_node->name ? of_node->name : "<unknown>";
 
-       /* we need the 'device_type' property, in order to match with drivers */
-       if (of_node->type == NULL) {
-               printk(KERN_WARNING "%s: node %s missing 'device_type'\n",
-                               __func__,
-                               of_node->name ? of_node->name : "<unknown>");
+       /*
+        * Determine if this node is a under the /vdevice node or under the
+        * /ibm,platform-facilities node.  This decides the device's family.
+        */
+       parent_node = of_get_parent(of_node);
+       if (parent_node) {
+               if (!strcmp(parent_node->full_name, "/ibm,platform-facilities"))
+                       family = PFO;
+               else if (!strcmp(parent_node->full_name, "/vdevice"))
+                       family = VDEVICE;
+               else {
+                       pr_warn("%s: parent(%s) of %s not recognized.\n",
+                                       __func__,
+                                       parent_node->full_name,
+                                       of_node_name);
+                       of_node_put(parent_node);
+                       return NULL;
+               }
+               of_node_put(parent_node);
+       } else {
+               pr_warn("%s: could not determine the parent of node %s.\n",
+                               __func__, of_node_name);
                return NULL;
        }
 
-       unit_address = of_get_property(of_node, "reg", NULL);
-       if (unit_address == NULL) {
-               printk(KERN_WARNING "%s: node %s missing 'reg'\n",
-                               __func__,
-                               of_node->name ? of_node->name : "<unknown>");
-               return NULL;
+       if (family == PFO) {
+               if (of_get_property(of_node, "interrupt-controller", NULL)) {
+                       pr_debug("%s: Skipping the interrupt controller %s.\n",
+                                       __func__, of_node_name);
+                       return NULL;
+               }
        }
 
        /* allocate a vio_dev for this node */
        viodev = kzalloc(sizeof(struct vio_dev), GFP_KERNEL);
-       if (viodev == NULL)
+       if (viodev == NULL) {
+               pr_warn("%s: allocation failure for VIO device.\n", __func__);
                return NULL;
+       }
 
-       viodev->irq = irq_of_parse_and_map(of_node, 0);
+       /* we need the 'device_type' property, in order to match with drivers */
+       viodev->family = family;
+       if (viodev->family == VDEVICE) {
+               if (of_node->type != NULL)
+                       viodev->type = of_node->type;
+               else {
+                       pr_warn("%s: node %s is missing the 'device_type' "
+                                       "property.\n", __func__, of_node_name);
+                       goto out;
+               }
+
+               unit_address = of_get_property(of_node, "reg", NULL);
+               if (unit_address == NULL) {
+                       pr_warn("%s: node %s missing 'reg'\n",
+                                       __func__, of_node_name);
+                       goto out;
+               }
+               dev_set_name(&viodev->dev, "%x", *unit_address);
+               viodev->irq = irq_of_parse_and_map(of_node, 0);
+               viodev->unit_address = *unit_address;
+       } else {
+               /* PFO devices need their resource_id for submitting COP_OPs
+                * This is an optional field for devices, but is required when
+                * performing synchronous ops */
+               pfo_resid = of_get_property(of_node, "ibm,resource-id", NULL);
+               if (pfo_resid != NULL)
+                       viodev->resource_id = *pfo_resid;
+
+               unit_address = NULL;
+               dev_set_name(&viodev->dev, "%s", of_node_name);
+               viodev->type = of_node_name;
+               viodev->irq = 0;
+       }
 
-       dev_set_name(&viodev->dev, "%x", *unit_address);
        viodev->name = of_node->name;
-       viodev->type = of_node->type;
-       viodev->unit_address = *unit_address;
        viodev->dev.of_node = of_node_get(of_node);
 
        if (firmware_has_feature(FW_FEATURE_CMO))
        }
 
        return viodev;
+
+out:   /* Use this exit point for any return prior to device_register */
+       kfree(viodev);
+
+       return NULL;
 }
 EXPORT_SYMBOL(vio_register_device_node);
 
+/*
+ * vio_bus_scan_for_devices - Scan OF and register each child device
+ * @root_name - OF node name for the root of the subtree to search.
+ *             This must be non-NULL
+ *
+ * Starting from the root node provide, register the device node for
+ * each child beneath the root.
+ */
+static void vio_bus_scan_register_devices(char *root_name)
+{
+       struct device_node *node_root, *node_child;
+
+       if (!root_name)
+               return;
+
+       node_root = of_find_node_by_name(NULL, root_name);
+       if (node_root) {
+
+               /*
+                * Create struct vio_devices for each virtual device in
+                * the device tree. Drivers will associate with them later.
+                */
+               node_child = of_get_next_child(node_root, NULL);
+               while (node_child) {
+                       vio_register_device_node(node_child);
+                       node_child = of_get_next_child(node_root, node_child);
+               }
+               of_node_put(node_root);
+       }
+}
+
 /**
  * vio_bus_init: - Initialize the virtual IO bus
  */
 static int __init vio_bus_init(void)
 {
        int err;
-       struct device_node *node_vroot;
 
        if (firmware_has_feature(FW_FEATURE_CMO))
                vio_cmo_sysfs_init();
        if (firmware_has_feature(FW_FEATURE_CMO))
                vio_cmo_bus_init();
 
-       node_vroot = of_find_node_by_name(NULL, "vdevice");
-       if (node_vroot) {
-               struct device_node *of_node;
-
-               /*
-                * Create struct vio_devices for each virtual device in
-                * the device tree. Drivers will associate with them later.
-                */
-               for (of_node = node_vroot->child; of_node != NULL;
-                               of_node = of_node->sibling)
-                       vio_register_device_node(of_node);
-               of_node_put(node_vroot);
-       }
+       vio_bus_scan_register_devices("vdevice");
+       vio_bus_scan_register_devices("ibm,platform-facilities");
 
        return 0;
 }
 {
        const uint32_t *unit_address;
        char kobj_name[20];
+       struct device_node *vnode_parent;
+       const char *dev_type;
+
+       vnode_parent = of_get_parent(vnode);
+       if (!vnode_parent)
+               return NULL;
+
+       dev_type = of_get_property(vnode_parent, "device_type", NULL);
+       of_node_put(vnode_parent);
+       if (!dev_type)
+               return NULL;
 
        /* construct the kobject name from the device node */
-       unit_address = of_get_property(vnode, "reg", NULL);
-       if (!unit_address)
+       if (!strcmp(dev_type, "vdevice")) {
+               unit_address = of_get_property(vnode, "reg", NULL);
+               if (!unit_address)
+                       return NULL;
+               snprintf(kobj_name, sizeof(kobj_name), "%x", *unit_address);
+       } else if (!strcmp(dev_type, "ibm,platform-facilities"))
+               snprintf(kobj_name, sizeof(kobj_name), "%s", vnode->name);
+       else
                return NULL;
-       snprintf(kobj_name, sizeof(kobj_name), "%x", *unit_address);
 
        return vio_find_name(kobj_name);
 }