goto fail_alloc1;
}
}
+ if (parent) {
+ int size = parent->driver->per_child_platdata_auto_alloc_size;
+
+ if (size) {
+ dev->flags |= DM_FLAG_ALLOC_PARENT_PDATA;
+ dev->parent_platdata = calloc(1, size);
+ if (!dev->parent_platdata) {
+ ret = -ENOMEM;
+ goto fail_alloc2;
+ }
+ }
+ }
/* put dev into parent's successor list */
if (parent)
dev->name);
}
fail_uclass_bind:
- if (parent)
- list_del(&dev->sibling_node);
+ list_del(&dev->sibling_node);
+ if (dev->flags & DM_FLAG_ALLOC_PARENT_PDATA) {
+ free(dev->parent_platdata);
+ dev->parent_platdata = NULL;
+ }
+fail_alloc2:
if (dev->flags & DM_FLAG_ALLOC_PDATA) {
free(dev->platdata);
dev->platdata = NULL;
return dev->platdata;
}
+void *dev_get_parent_platdata(struct udevice *dev)
+{
+ if (!dev) {
+ dm_warn("%s: null device", __func__);
+ return NULL;
+ }
+
+ return dev->parent_platdata;
+}
+
void *dev_get_priv(struct udevice *dev)
{
if (!dev) {
/* DM should init this device prior to relocation */
#define DM_FLAG_PRE_RELOC (1 << 2)
+/* DM is responsible for allocating and freeing parent_platdata */
+#define DM_FLAG_ALLOC_PARENT_PDATA (1 << 3)
+
/**
* struct udevice - An instance of a driver
*
* @driver: The driver used by this device
* @name: Name of device, typically the FDT node name
* @platdata: Configuration data for this device
+ * @parent_platdata: The parent bus's configuration data for this device
* @of_offset: Device tree node offset for this device (- for none)
* @of_id: Pointer to the udevice_id structure which created the device
* @parent: Parent of this device, or NULL for the top level device
struct driver *driver;
const char *name;
void *platdata;
+ void *parent_platdata;
int of_offset;
const struct udevice_id *of_id;
struct udevice *parent;
* device_probe_child() pass it in. So far the use case for allocating it
* is SPI, but I found that unsatisfactory. Since it is here I will leave it
* until things are clearer.
+ * @per_child_platdata_auto_alloc_size: A bus likes to store information about
+ * its children. If non-zero this is the size of this data, to be allocated
+ * in the child's parent_platdata pointer.
* @ops: Driver-specific operations. This is typically a list of function
* pointers defined by the driver, to implement driver functions required by
* the uclass.
int priv_auto_alloc_size;
int platdata_auto_alloc_size;
int per_child_auto_alloc_size;
+ int per_child_platdata_auto_alloc_size;
const void *ops; /* driver-specific operations */
uint32_t flags;
};
*/
void *dev_get_platdata(struct udevice *dev);
+/**
+ * dev_get_parent_platdata() - Get the parent platform data for a device
+ *
+ * This checks that dev is not NULL, but no other checks for now
+ *
+ * @dev Device to check
+ * @return parent's platform data, or NULL if none
+ */
+void *dev_get_parent_platdata(struct udevice *dev);
+
/**
* dev_get_parentdata() - Get the parent data for a device
*
#include <dm/device-internal.h>
#include <dm/root.h>
#include <dm/test.h>
+#include <dm/uclass-internal.h>
#include <dm/ut.h>
#include <dm/util.h>
DECLARE_GLOBAL_DATA_PTR;
+struct dm_test_parent_platdata {
+ int count;
+};
+
enum {
FLAG_CHILD_PROBED = 10,
FLAG_CHILD_REMOVED = -7,
.priv_auto_alloc_size = sizeof(struct dm_test_priv),
.platdata_auto_alloc_size = sizeof(struct dm_test_pdata),
.per_child_auto_alloc_size = sizeof(struct dm_test_parent_data),
+ .per_child_platdata_auto_alloc_size =
+ sizeof(struct dm_test_parent_platdata),
.child_pre_probe = testbus_child_pre_probe,
.child_post_remove = testbus_child_post_remove,
};
return 0;
}
DM_TEST(dm_test_bus_parent_ops, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Test that the bus can store platform data about each child */
+static int dm_test_bus_parent_platdata(struct dm_test_state *dms)
+{
+ struct dm_test_parent_platdata *plat;
+ struct udevice *bus, *dev;
+ int child_count;
+
+ /* Check that the bus has no children */
+ ut_assertok(uclass_find_device(UCLASS_TEST_BUS, 0, &bus));
+ device_find_first_child(bus, &dev);
+ ut_asserteq_ptr(NULL, dev);
+
+ ut_assertok(uclass_get_device(UCLASS_TEST_BUS, 0, &bus));
+
+ for (device_find_first_child(bus, &dev), child_count = 0;
+ dev;
+ device_find_next_child(&dev)) {
+ /* Check that platform data is allocated */
+ plat = dev_get_parent_platdata(dev);
+ ut_assert(plat != NULL);
+
+ /*
+ * Check that it is not affected by the device being
+ * probed/removed
+ */
+ plat->count++;
+ ut_asserteq(1, plat->count);
+ device_probe(dev);
+ device_remove(dev);
+
+ ut_asserteq_ptr(plat, dev_get_parent_platdata(dev));
+ ut_asserteq(1, plat->count);
+ ut_assertok(device_probe(dev));
+ child_count++;
+ }
+ ut_asserteq(3, child_count);
+
+ /* Removing the bus should also have no effect (it is still bound) */
+ device_remove(bus);
+ for (device_find_first_child(bus, &dev), child_count = 0;
+ dev;
+ device_find_next_child(&dev)) {
+ /* Check that platform data is allocated */
+ plat = dev_get_parent_platdata(dev);
+ ut_assert(plat != NULL);
+ ut_asserteq(1, plat->count);
+ child_count++;
+ }
+ ut_asserteq(3, child_count);
+
+ /* Unbind all the children */
+ do {
+ device_find_first_child(bus, &dev);
+ if (dev)
+ device_unbind(dev);
+ } while (dev);
+
+ /* Now the child platdata should be removed and re-added */
+ device_probe(bus);
+ for (device_find_first_child(bus, &dev), child_count = 0;
+ dev;
+ device_find_next_child(&dev)) {
+ /* Check that platform data is allocated */
+ plat = dev_get_parent_platdata(dev);
+ ut_assert(plat != NULL);
+ ut_asserteq(0, plat->count);
+ child_count++;
+ }
+ ut_asserteq(3, child_count);
+
+ return 0;
+}
+DM_TEST(dm_test_bus_parent_platdata, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);