[DRIVER MODEL] Improved dynamically allocated platform_device interface

Re-jig the simple platform device support to allow private data
to be attached to a platform device, as well as allowing the
parent device to be set.

Example usage:

	pdev = platform_device_alloc("mydev", id);
	if (pdev) {
		err = platform_device_add_resources(pdev, &resources,
						    ARRAY_SIZE(resources));
		if (err == 0)
			err = platform_device_add_data(pdev, &platform_data,
						       sizeof(platform_data));
		if (err == 0)
			err = platform_device_add(pdev);
	} else {
		err = -ENOMEM;
	}
	if (err)
		platform_device_put(pdev);

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index d597c92..6d4736e 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -116,12 +116,115 @@
 	return ret;
 }
 
+struct platform_object {
+	struct platform_device pdev;
+	char name[1];
+};
+
 /**
- *	platform_device_register - add a platform-level device
+ *	platform_device_put
+ *	@pdev:	platform device to free
+ *
+ *	Free all memory associated with a platform device.  This function
+ *	must _only_ be externally called in error cases.  All other usage
+ *	is a bug.
+ */
+void platform_device_put(struct platform_device *pdev)
+{
+	if (pdev)
+		put_device(&pdev->dev);
+}
+EXPORT_SYMBOL_GPL(platform_device_put);
+
+static void platform_device_release(struct device *dev)
+{
+	struct platform_object *pa = container_of(dev, struct platform_object, pdev.dev);
+
+	kfree(pa->pdev.dev.platform_data);
+	kfree(pa->pdev.resource);
+	kfree(pa);
+}
+
+/**
+ *	platform_device_alloc
+ *	@name:	base name of the device we're adding
+ *	@id:    instance id
+ *
+ *	Create a platform device object which can have other objects attached
+ *	to it, and which will have attached objects freed when it is released.
+ */
+struct platform_device *platform_device_alloc(const char *name, unsigned int id)
+{
+	struct platform_object *pa;
+
+	pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);
+	if (pa) {
+		strcpy(pa->name, name);
+		pa->pdev.name = pa->name;
+		pa->pdev.id = id;
+		device_initialize(&pa->pdev.dev);
+		pa->pdev.dev.release = platform_device_release;
+	}
+
+	return pa ? &pa->pdev : NULL;	
+}
+EXPORT_SYMBOL_GPL(platform_device_alloc);
+
+/**
+ *	platform_device_add_resources
+ *	@pdev:	platform device allocated by platform_device_alloc to add resources to
+ *	@res:   set of resources that needs to be allocated for the device
+ *	@num:	number of resources
+ *
+ *	Add a copy of the resources to the platform device.  The memory
+ *	associated with the resources will be freed when the platform
+ *	device is released.
+ */
+int platform_device_add_resources(struct platform_device *pdev, struct resource *res, unsigned int num)
+{
+	struct resource *r;
+
+	r = kmalloc(sizeof(struct resource) * num, GFP_KERNEL);
+	if (r) {
+		memcpy(r, res, sizeof(struct resource) * num);
+		pdev->resource = r;
+		pdev->num_resources = num;
+	}
+	return r ? 0 : -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(platform_device_add_resources);
+
+/**
+ *	platform_device_add_data
+ *	@pdev:	platform device allocated by platform_device_alloc to add resources to
+ *	@data:	platform specific data for this platform device
+ *	@size:	size of platform specific data
+ *
+ *	Add a copy of platform specific data to the platform device's platform_data
+ *	pointer.  The memory associated with the platform data will be freed
+ *	when the platform device is released.
+ */
+int platform_device_add_data(struct platform_device *pdev, void *data, size_t size)
+{
+	void *d;
+
+	d = kmalloc(size, GFP_KERNEL);
+	if (d) {
+		memcpy(d, data, size);
+		pdev->dev.platform_data = d;
+	}
+	return d ? 0 : -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(platform_device_add_data);
+
+/**
+ *	platform_device_add - add a platform device to device hierarchy
  *	@pdev:	platform device we're adding
  *
+ *	This is part 2 of platform_device_register(), though may be called
+ *	separately _iff_ pdev was allocated by platform_device_alloc().
  */
-int platform_device_register(struct platform_device * pdev)
+int platform_device_add(struct platform_device *pdev)
 {
 	int i, ret = 0;
 
@@ -174,6 +277,18 @@
 			release_resource(&pdev->resource[i]);
 	return ret;
 }
+EXPORT_SYMBOL_GPL(platform_device_add);
+
+/**
+ *	platform_device_register - add a platform-level device
+ *	@pdev:	platform device we're adding
+ *
+ */
+int platform_device_register(struct platform_device * pdev)
+{
+	device_initialize(&pdev->dev);
+	return platform_device_add(pdev);
+}
 
 /**
  *	platform_device_unregister - remove a platform-level device
@@ -197,18 +312,6 @@
 	}
 }
 
-struct platform_object {
-        struct platform_device pdev;
-        struct resource resources[0];
-};
-
-static void platform_device_release_simple(struct device *dev)
-{
-	struct platform_device *pdev = to_platform_device(dev);
-
-	kfree(container_of(pdev, struct platform_object, pdev));
-}
-
 /**
  *	platform_device_register_simple
  *	@name:  base name of the device we're adding
@@ -225,33 +328,29 @@
 struct platform_device *platform_device_register_simple(char *name, unsigned int id,
 							struct resource *res, unsigned int num)
 {
-	struct platform_object *pobj;
+	struct platform_device *pdev;
 	int retval;
 
-	pobj = kzalloc(sizeof(*pobj) + sizeof(struct resource) * num, GFP_KERNEL);
-	if (!pobj) {
+	pdev = platform_device_alloc(name, id);
+	if (!pdev) {
 		retval = -ENOMEM;
 		goto error;
 	}
 
-	pobj->pdev.name = name;
-	pobj->pdev.id = id;
-	pobj->pdev.dev.release = platform_device_release_simple;
-
 	if (num) {
-		memcpy(pobj->resources, res, sizeof(struct resource) * num);
-		pobj->pdev.resource = pobj->resources;
-		pobj->pdev.num_resources = num;
+		retval = platform_device_add_resources(pdev, res, num);
+		if (retval)
+			goto error;
 	}
 
-	retval = platform_device_register(&pobj->pdev);
+	retval = platform_device_add(pdev);
 	if (retval)
 		goto error;
 
-	return &pobj->pdev;
+	return pdev;
 
 error:
-	kfree(pobj);
+	platform_device_put(pdev);
 	return ERR_PTR(retval);
 }