of_platform_populate - notro/rpi-firmware GitHub Wiki

Adding devices from Device Tree

arch/arm/mach-bcm2835/bcm2835.c

static void __init bcm2835_init(void)
{
        int ret;

        bcm2835_setup_restart();
        if (wdt_regs)
                pm_power_off = bcm2835_power_off;

        bcm2835_init_clocks();

        ret = of_platform_populate(NULL, of_default_bus_match_table, NULL,
                                   NULL);
        if (ret) {
                pr_err("of_platform_populate failed: %d\n", ret);
                BUG();
        }
}

drivers/of/device.c


const struct of_device_id of_default_bus_match_table[] = {
        { .compatible = "simple-bus", },
        {} /* Empty terminated list */
};


int of_platform_populate(struct device_node *root,
                        const struct of_device_id *matches,
                        const struct of_dev_auxdata *lookup,
                        struct device *parent)
{
        struct device_node *child;
        int rc = 0;

        root = root ? of_node_get(root) : of_find_node_by_path("/");
        if (!root)
                return -EINVAL;

        for_each_child_of_node(root, child) {
                rc = of_platform_bus_create(child, matches, lookup, parent, true);
                if (rc)
                        break;
        }

        of_node_put(root);
        return rc;
}

static int of_platform_bus_create(struct device_node *bus,
                                  const struct of_device_id *matches,
                                  const struct of_dev_auxdata *lookup,
                                  struct device *parent, bool strict)
{
        const struct of_dev_auxdata *auxdata;
        struct device_node *child;
        struct platform_device *dev;
        const char *bus_id = NULL;
        void *platform_data = NULL;
        int rc = 0;

        /* Make sure it has a compatible property */
        if (strict && (!of_get_property(bus, "compatible", NULL))) {
                pr_debug("%s() - skipping %s, no compatible prop\n",
                         __func__, bus->full_name);
                return 0;
        }

        auxdata = of_dev_lookup(lookup, bus);
        if (auxdata) {
                bus_id = auxdata->name;
                platform_data = auxdata->platform_data;
        }

        if (of_device_is_compatible(bus, "arm,primecell")) {
                /*
                 * Don't return an error here to keep compatibility with older
                 * device tree files.
                 */
                of_amba_device_create(bus, bus_id, platform_data, parent);
                return 0;
        }

        dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
        if (!dev || !of_match_node(matches, bus))
                return 0;

        for_each_child_of_node(bus, child) {
                pr_debug("   create child: %s\n", child->full_name);
                rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
                if (rc) {
                        of_node_put(child);
                        break;
                }
        }
        return rc;
}

/**
 * of_platform_device_create_pdata - Alloc, initialize and register an of_device
 * @np: pointer to node to create device for
 * @bus_id: name to assign device
 * @platform_data: pointer to populate platform_data pointer with
 * @parent: Linux device model parent device.
 *
 * Returns pointer to created platform device, or NULL if a device was not
 * registered.  Unavailable devices will not get registered.
 */
static struct platform_device *of_platform_device_create_pdata(
                                        struct device_node *np,
                                        const char *bus_id,
                                        void *platform_data,
                                        struct device *parent)
{
        struct platform_device *dev;

        if (!of_device_is_available(np))
                return NULL;

        dev = of_device_alloc(np, bus_id, parent);
        if (!dev)
                return NULL;

#if defined(CONFIG_MICROBLAZE)
        dev->archdata.dma_mask = 0xffffffffUL;
#endif
        dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
        dev->dev.bus = &platform_bus_type;
        dev->dev.platform_data = platform_data;

        /* We do not fill the DMA ops for platform devices by default.
         * This is currently the responsibility of the platform code
         * to do such, possibly using a device notifier
         */

        if (of_device_add(dev) != 0) {
                platform_device_put(dev);
                return NULL;
        }

        return dev;
}

/**
 * of_device_alloc - Allocate and initialize an of_device
 * @np: device node to assign to device
 * @bus_id: Name to assign to the device.  May be null to use default name.
 * @parent: Parent device.
 */
struct platform_device *of_device_alloc(struct device_node *np,
                                  const char *bus_id,
                                  struct device *parent)
{
        struct platform_device *dev;
        int rc, i, num_reg = 0, num_irq;
        struct resource *res, temp_res;

        dev = platform_device_alloc("", -1);
        if (!dev)
                return NULL;

        /* count the io and irq resources */
        if (of_can_translate_address(np))
                while (of_address_to_resource(np, num_reg, &temp_res) == 0)
                        num_reg++;
        num_irq = of_irq_count(np);

        /* Populate the resource table */
        if (num_irq || num_reg) {
                res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
                if (!res) {
                        platform_device_put(dev);
                        return NULL;
                }

                dev->num_resources = num_reg + num_irq;
                dev->resource = res;
                for (i = 0; i < num_reg; i++, res++) {
                        rc = of_address_to_resource(np, i, res);
                        WARN_ON(rc);
                }
                WARN_ON(of_irq_to_resource_table(np, res, num_irq) != num_irq);
        }

        dev->dev.of_node = of_node_get(np);
#if defined(CONFIG_MICROBLAZE)
        dev->dev.dma_mask = &dev->archdata.dma_mask;
#endif
        dev->dev.parent = parent;

        if (bus_id)
                dev_set_name(&dev->dev, "%s", bus_id);
        else
                of_device_make_bus_id(&dev->dev);

        return dev;
}

int of_device_add(struct platform_device *ofdev)
{
        BUG_ON(ofdev->dev.of_node == NULL);

        /* name and id have to be set so that the platform bus doesn't get
         * confused on matching */
        ofdev->name = dev_name(&ofdev->dev);
        ofdev->id = -1;

        /* device_add will assume that this device is on the same node as
         * the parent. If there is no parent defined, set the node
         * explicitly */
        if (!ofdev->dev.parent)
                set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));

        return device_add(&ofdev->dev);
}

drivers/base/core.c

int device_add(struct device *dev)
{
        struct device *parent = NULL;
        struct kobject *kobj;
        struct class_interface *class_intf;
        int error = -EINVAL;

        dev = get_device(dev);
        if (!dev)
                goto done;

        if (!dev->p) {
                error = device_private_init(dev);
                if (error)
                        goto done;
        }

        /*
         * for statically allocated devices, which should all be converted
         * some day, we need to initialize the name. We prevent reading back
         * the name, and force the use of dev_name()
         */
        if (dev->init_name) {
                dev_set_name(dev, "%s", dev->init_name);
                dev->init_name = NULL;
        }

        /* subsystems can specify simple device enumeration */
        if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
                dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

        if (!dev_name(dev)) {
                error = -EINVAL;
                goto name_error;
        }

        pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

        parent = get_device(dev->parent);
        kobj = get_device_parent(dev, parent);
        if (kobj)
                dev->kobj.parent = kobj;

        /* use parent numa_node */
        if (parent)
                set_dev_node(dev, dev_to_node(parent));

        /* first, register with generic layer. */
        /* we require the name to be set before, and pass NULL */
        error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
        if (error)
                goto Error;

        /* notify platform of device entry */
        if (platform_notify)
                platform_notify(dev);

        error = device_create_file(dev, &dev_attr_uevent);
        if (error)
                goto attrError;

        if (MAJOR(dev->devt)) {
                error = device_create_file(dev, &dev_attr_dev);
                if (error)
                        goto ueventattrError;

                error = device_create_sys_dev_entry(dev);
                if (error)
                        goto devtattrError;

                devtmpfs_create_node(dev);
        }

        error = device_add_class_symlinks(dev);
        if (error)
                goto SymlinkError;
        error = device_add_attrs(dev);
        if (error)
                goto AttrsError;
        error = bus_add_device(dev);
        if (error)
                goto BusError;
        error = dpm_sysfs_add(dev);
        if (error)
                goto DPMError;
        device_pm_add(dev);

        /* Notify clients of device addition.  This call must come
         * after dpm_sysfs_add() and before kobject_uevent().
         */
        if (dev->bus)
                blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                                             BUS_NOTIFY_ADD_DEVICE, dev);

        kobject_uevent(&dev->kobj, KOBJ_ADD);
        bus_probe_device(dev);
        if (parent)
                klist_add_tail(&dev->p->knode_parent,
                               &parent->p->klist_children);

        if (dev->class) {
                mutex_lock(&dev->class->p->mutex);
                /* tie the class to the device */
                klist_add_tail(&dev->knode_class,
                               &dev->class->p->klist_devices);

                /* notify any interfaces that the device is here */
                list_for_each_entry(class_intf,
                                    &dev->class->p->interfaces, node)
                        if (class_intf->add_dev)
                                class_intf->add_dev(dev, class_intf);
                mutex_unlock(&dev->class->p->mutex);
        }
done:
        put_device(dev);
        return error;
 DPMError:
        bus_remove_device(dev);
 BusError:
        device_remove_attrs(dev);
 AttrsError:
        device_remove_class_symlinks(dev);
 SymlinkError:
        if (MAJOR(dev->devt))
                devtmpfs_delete_node(dev);
        if (MAJOR(dev->devt))
                device_remove_sys_dev_entry(dev);
 devtattrError:
        if (MAJOR(dev->devt))
                device_remove_file(dev, &dev_attr_dev);
 ueventattrError:
        device_remove_file(dev, &dev_attr_uevent);
 attrError:
        kobject_uevent(&dev->kobj, KOBJ_REMOVE);
        kobject_del(&dev->kobj);
 Error:
        cleanup_device_parent(dev);
        if (parent)
                put_device(parent);
name_error:
        kfree(dev->p);
        dev->p = NULL;
        goto done;
}

piwik