summaryrefslogtreecommitdiffstats
path: root/drivers/base/driver.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base/driver.c')
-rw-r--r--drivers/base/driver.c464
1 files changed, 395 insertions, 69 deletions
diff --git a/drivers/base/driver.c b/drivers/base/driver.c
index eec2a2d8a2..fbc5cbebe0 100644
--- a/drivers/base/driver.c
+++ b/drivers/base/driver.c
@@ -1,20 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* driver.c - barebox driver model
*
* Copyright (c) 2007 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
- *
- * See file CREDITS for list of people who contributed to this
- * project.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
*/
/**
@@ -22,8 +10,11 @@
* @brief barebox's driver model, and devinfo command
*/
+#define dev_err_probe dev_err_probe
+
#include <common.h>
#include <command.h>
+#include <deep-probe.h>
#include <driver.h>
#include <malloc.h>
#include <console.h>
@@ -33,9 +24,18 @@
#include <fs.h>
#include <of.h>
#include <linux/list.h>
+#include <linux/overflow.h>
#include <linux/err.h>
#include <complete.h>
#include <pinctrl.h>
+#include <featctrl.h>
+#include <linux/clk/clk-conf.h>
+
+#ifdef CONFIG_DEBUG_PROBES
+#define pr_report_probe pr_info
+#else
+#define pr_report_probe pr_debug
+#endif
LIST_HEAD(device_list);
EXPORT_SYMBOL(device_list);
@@ -43,24 +43,49 @@ EXPORT_SYMBOL(device_list);
LIST_HEAD(driver_list);
EXPORT_SYMBOL(driver_list);
-static LIST_HEAD(active);
+LIST_HEAD(active_device_list);
+EXPORT_SYMBOL(active_device_list);
static LIST_HEAD(deferred);
-struct device_d *get_device_by_name(const char *name)
+static LIST_HEAD(device_alias_list);
+
+struct device *find_device(const char *str)
{
- struct device_d *dev;
+ struct device *dev;
+ struct device_node *np;
+
+ dev = get_device_by_name(str);
+ if (dev)
+ return dev;
+
+ np = of_find_node_by_path_or_alias(NULL, str);
+ if (np)
+ return of_find_device_by_node(np);
+
+ return NULL;
+}
+
+struct device *get_device_by_name(const char *name)
+{
+ struct device *dev;
+ struct device_alias *alias;
for_each_device(dev) {
if(!strcmp(dev_name(dev), name))
return dev;
}
+ list_for_each_entry(alias, &device_alias_list, list) {
+ if(!strcmp(alias->name, name))
+ return alias->dev;
+ }
+
return NULL;
}
-static struct device_d *get_device_by_name_id(const char *name, int id)
+static struct device *get_device_by_name_id(const char *name, int id)
{
- struct device_d *dev;
+ struct device *dev;
for_each_device(dev) {
if(!strcmp(dev->name, name) && id == dev->id)
@@ -81,34 +106,78 @@ int get_free_deviceid(const char *name_template)
};
}
-int device_probe(struct device_d *dev)
+static void dev_report_permanent_probe_deferral(struct device *dev)
{
+ if (dev->deferred_probe_reason)
+ dev_err(dev, "probe permanently deferred (%s)\n",
+ dev->deferred_probe_reason);
+ else
+ dev_err(dev, "probe permanently deferred\n");
+}
+
+int device_probe(struct device *dev)
+{
+ static int depth = 0;
int ret;
+ ret = of_feature_controller_check(dev->of_node);
+ if (ret < 0)
+ return ret;
+ if (ret == FEATCTRL_GATED) {
+ dev_dbg(dev, "feature gated, skipping probe\n");
+ return -ENODEV;
+ }
+
+ depth++;
+
+ pr_report_probe("%*sprobe-> %s\n", depth * 4, "", dev_name(dev));
+
pinctrl_select_state_default(dev);
+ of_clk_set_defaults(dev->of_node, false);
+
+ list_add(&dev->active, &active_device_list);
- list_add(&dev->active, &active);
+ if (dev->bus->probe)
+ ret = dev->bus->probe(dev);
+ else if (dev->driver->probe)
+ ret = dev->driver->probe(dev);
+ else
+ ret = 0;
- ret = dev->bus->probe(dev);
- if (ret == 0)
+ depth--;
+
+ switch (ret) {
+ case 0:
return 0;
+ case -EPROBE_DEFER:
+ /*
+ * -EPROBE_DEFER should never appear on a deep-probe machine so
+ * inform the user immediately.
+ */
+ if (deep_probe_is_supported()) {
+ dev_report_permanent_probe_deferral(dev);
+ break;
+ }
+
+ list_move(&dev->active, &deferred);
- if (ret == -EPROBE_DEFER) {
- list_del(&dev->active);
- list_add(&dev->active, &deferred);
dev_dbg(dev, "probe deferred\n");
- return ret;
+ return -EPROBE_DEFER;
+ case -ENODEV:
+ case -ENXIO:
+ dev_dbg(dev, "probe failed: %pe\n", ERR_PTR(ret));
+ break;
+ default:
+ dev_err(dev, "probe failed: %pe\n", ERR_PTR(ret));
+ break;
}
- list_del(&dev->active);
- INIT_LIST_HEAD(&dev->active);
-
- dev_err(dev, "probe failed: %s\n", strerror(-ret));
+ list_del_init(&dev->active);
return ret;
}
-int device_detect(struct device_d *dev)
+int device_detect(struct device *dev)
{
if (!dev->detect)
return -ENOSYS;
@@ -119,7 +188,7 @@ int device_detect_by_name(const char *__devname)
{
char *devname = xstrdup(__devname);
char *str = devname;
- struct device_d *dev;
+ struct device *dev;
int ret = -ENODEV;
while (1) {
@@ -142,13 +211,13 @@ int device_detect_by_name(const char *__devname)
void device_detect_all(void)
{
- struct device_d *dev;
+ struct device *dev;
for_each_device(dev)
device_detect(dev);
}
-static int match(struct driver_d *drv, struct device_d *dev)
+static int match(struct driver *drv, struct device *dev)
{
int ret;
@@ -157,7 +226,7 @@ static int match(struct driver_d *drv, struct device_d *dev)
dev->driver = drv;
- if (dev->bus->match(dev, drv))
+ if (dev->bus->match && dev->bus->match(dev, drv))
goto err_out;
ret = device_probe(dev);
if (ret)
@@ -169,9 +238,9 @@ err_out:
return -1;
}
-int register_device(struct device_d *new_device)
+int register_device(struct device *new_device)
{
- struct driver_d *drv;
+ struct driver *drv;
if (new_device->id == DEVICE_ID_DYNAMIC) {
new_device->id = get_free_deviceid(new_device->name);
@@ -216,17 +285,24 @@ int register_device(struct device_d *new_device)
}
EXPORT_SYMBOL(register_device);
-int unregister_device(struct device_d *old_dev)
+int unregister_device(struct device *old_dev)
{
+ struct device_alias *alias, *at;
struct cdev *cdev, *ct;
- struct device_d *child, *dt;
+ struct device *child, *dt;
+ struct device_node *np;
dev_dbg(old_dev, "unregister\n");
dev_remove_parameters(old_dev);
if (old_dev->driver)
- old_dev->bus->remove(old_dev);
+ device_remove(old_dev);
+
+ list_for_each_entry_safe(alias, at, &device_alias_list, list) {
+ if(alias->dev == old_dev)
+ list_del(&alias->list);
+ }
list_for_each_entry_safe(child, dt, &old_dev->children, sibling) {
dev_dbg(old_dev, "unregister child %s\n", dev_name(child));
@@ -234,9 +310,9 @@ int unregister_device(struct device_d *old_dev)
}
list_for_each_entry_safe(cdev, ct, &old_dev->cdevs, devices_list) {
- if (cdev->master) {
+ if (cdev_is_partition(cdev)) {
dev_dbg(old_dev, "unregister part %s\n", cdev->name);
- devfs_del_partition(cdev->name);
+ cdevfs_del_partition(cdev);
}
}
@@ -248,10 +324,45 @@ int unregister_device(struct device_d *old_dev)
if (old_dev->parent)
list_del(&old_dev->sibling);
+ np = dev_of_node(old_dev);
+ if (np && np->dev == old_dev)
+ np->dev = NULL;
+
return 0;
}
EXPORT_SYMBOL(unregister_device);
+/**
+ * free_device_res - free dynamically allocated device members
+ * @dev: The device
+ *
+ * This frees dynamically allocated resources allocated during device
+ * lifetime, but not the device itself.
+ */
+void free_device_res(struct device *dev)
+{
+ free(dev->name);
+ dev->name = NULL;
+ free(dev->unique_name);
+ dev->unique_name = NULL;
+ free(dev->deferred_probe_reason);
+}
+EXPORT_SYMBOL(free_device_res);
+
+/**
+ * free_device - free a device
+ * @dev: The device
+ *
+ * This frees dynamically allocated resources allocated during device
+ * lifetime and finally the device itself.
+ */
+void free_device(struct device *dev)
+{
+ free_device_res(dev);
+ free(dev);
+}
+EXPORT_SYMBOL(free_device);
+
/*
* Loop over list of deferred devices as long as at least one
* device is successfully probed. Devices that again request
@@ -261,8 +372,8 @@ EXPORT_SYMBOL(unregister_device);
*/
static int device_probe_deferred(void)
{
- struct device_d *dev, *tmp;
- struct driver_d *drv;
+ struct device *dev, *tmp;
+ struct driver *drv;
bool success;
do {
@@ -286,15 +397,15 @@ static int device_probe_deferred(void)
} while (success);
list_for_each_entry(dev, &deferred, active)
- dev_err(dev, "probe permanently deferred\n");
+ dev_report_permanent_probe_deferral(dev);
return 0;
}
late_initcall(device_probe_deferred);
-struct driver_d *get_driver_by_name(const char *name)
+struct driver *get_driver_by_name(const char *name)
{
- struct driver_d *drv;
+ struct driver *drv;
for_each_driver(drv) {
if(!strcmp(name, drv->name))
@@ -304,9 +415,12 @@ struct driver_d *get_driver_by_name(const char *name)
return NULL;
}
-int register_driver(struct driver_d *drv)
+int register_driver(struct driver *drv)
{
- struct device_d *dev = NULL;
+ struct device *dev = NULL;
+
+ if (!drv->name)
+ return -EINVAL;
debug("register_driver: %s\n", drv->name);
@@ -322,7 +436,24 @@ int register_driver(struct driver_d *drv)
}
EXPORT_SYMBOL(register_driver);
-struct resource *dev_get_resource(struct device_d *dev, unsigned long type,
+void unregister_driver(struct driver *drv)
+{
+ struct device *dev;
+
+ list_del(&drv->list);
+ list_del(&drv->bus_list);
+
+ bus_for_each_device(drv->bus, dev) {
+ if (dev->driver == drv) {
+ device_remove(dev);
+ dev->driver = NULL;
+ list_del(&dev->active);
+ INIT_LIST_HEAD(&dev->active);
+ }
+ }
+}
+
+struct resource *dev_get_resource(struct device *dev, unsigned long type,
int num)
{
int i, n = 0;
@@ -339,7 +470,7 @@ struct resource *dev_get_resource(struct device_d *dev, unsigned long type,
return ERR_PTR(-ENOENT);
}
-void *dev_get_mem_region(struct device_d *dev, int num)
+void *dev_get_mem_region(struct device *dev, int num)
{
struct resource *res;
@@ -351,7 +482,7 @@ void *dev_get_mem_region(struct device_d *dev, int num)
}
EXPORT_SYMBOL(dev_get_mem_region);
-struct resource *dev_get_resource_by_name(struct device_d *dev,
+struct resource *dev_get_resource_by_name(struct device *dev,
unsigned long type,
const char *name)
{
@@ -370,7 +501,19 @@ struct resource *dev_get_resource_by_name(struct device_d *dev,
return ERR_PTR(-ENOENT);
}
-void __iomem *dev_request_mem_region_by_name(struct device_d *dev, const char *name)
+static struct resource *dev_request_iomem_resource(struct device *dev,
+ const struct resource *res)
+{
+ return request_iomem_region(dev_name(dev), res->start, res->end);
+}
+
+int dev_request_resource(struct device *dev, const struct resource *res)
+{
+ return PTR_ERR_OR_ZERO(dev_request_iomem_resource(dev, res));
+}
+EXPORT_SYMBOL(dev_request_resource);
+
+struct resource *dev_request_mem_resource_by_name(struct device *dev, const char *name)
{
struct resource *res;
@@ -378,7 +521,16 @@ void __iomem *dev_request_mem_region_by_name(struct device_d *dev, const char *n
if (IS_ERR(res))
return ERR_CAST(res);
- res = request_iomem_region(dev_name(dev), res->start, res->end);
+ return dev_request_iomem_resource(dev, res);
+}
+EXPORT_SYMBOL(dev_request_mem_resource_by_name);
+
+void __iomem *dev_request_mem_region_by_name(struct device *dev,
+ const char *name)
+{
+ struct resource *res;
+
+ res = dev_request_mem_resource_by_name(dev, name);
if (IS_ERR(res))
return ERR_CAST(res);
@@ -386,7 +538,26 @@ void __iomem *dev_request_mem_region_by_name(struct device_d *dev, const char *n
}
EXPORT_SYMBOL(dev_request_mem_region_by_name);
-struct resource *dev_request_mem_resource(struct device_d *dev, int num)
+void __iomem *dev_platform_get_and_ioremap_resource(struct device *dev,
+ int num,
+ struct resource **out_res)
+{
+ struct resource *res;
+
+ res = dev_request_mem_resource(dev, num);
+ if (IS_ERR(res))
+ return IOMEM_ERR_PTR(PTR_ERR(res));
+ else if (WARN_ON(IS_ERR_VALUE(res->start)))
+ return IOMEM_ERR_PTR(-EINVAL);
+
+ if (out_res)
+ *out_res = res;
+
+ return IOMEM(res->start);
+}
+EXPORT_SYMBOL(dev_platform_get_and_ioremap_resource);
+
+struct resource *dev_request_mem_resource(struct device *dev, int num)
{
struct resource *res;
@@ -394,22 +565,22 @@ struct resource *dev_request_mem_resource(struct device_d *dev, int num)
if (IS_ERR(res))
return ERR_CAST(res);
- return request_iomem_region(dev_name(dev), res->start, res->end);
+ return dev_request_iomem_resource(dev, res);
}
-void __iomem *dev_request_mem_region_err_null(struct device_d *dev, int num)
+void __iomem *dev_request_mem_region_err_null(struct device *dev, int num)
{
struct resource *res;
res = dev_request_mem_resource(dev, num);
- if (IS_ERR(res))
+ if (IS_ERR(res) || WARN_ON(!res->start))
return NULL;
return IOMEM(res->start);
}
EXPORT_SYMBOL(dev_request_mem_region_err_null);
-void __iomem *dev_request_mem_region(struct device_d *dev, int num)
+void __iomem *dev_request_mem_region(struct device *dev, int num)
{
struct resource *res;
@@ -446,9 +617,9 @@ int generic_memmap_ro(struct cdev *cdev, void **map, int flags)
*
* NOTE: This function expects dev->name to be free()-able, so extra
* precautions needs to be taken when mixing its usage with manual
- * assignement of device_d.name.
+ * assignement of device.name.
*/
-int dev_set_name(struct device_d *dev, const char *fmt, ...)
+int dev_set_name(struct device *dev, const char *fmt, ...)
{
va_list vargs;
int err;
@@ -467,24 +638,67 @@ int dev_set_name(struct device_d *dev, const char *fmt, ...)
*/
free(oldname);
- WARN_ON(err < 0);
-
- return err;
+ return WARN_ON(err < 0) ? err : 0;
}
EXPORT_SYMBOL_GPL(dev_set_name);
+/**
+ * dev_add_alias - add alias for device
+ * @dev: device
+ * @fmt: format string for the device's alias
+ */
+int dev_add_alias(struct device *dev, const char *fmt, ...)
+{
+ va_list va, va_copy;
+ unsigned int len;
+ struct device_alias *alias;
+
+ va_start(va, fmt);
+ va_copy(va_copy, va);
+ len = vsnprintf(NULL, 0, fmt, va_copy);
+ va_end(va_copy);
+
+ alias = malloc(struct_size(alias, name, len + 1));
+ if (!alias)
+ return -ENOMEM;
+
+ vsnprintf(alias->name, len + 1, fmt, va);
+
+ va_end(va);
+
+ alias->dev = dev;
+ list_add_tail(&alias->list, &device_alias_list);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dev_add_alias);
+
+bool device_remove(struct device *dev)
+{
+ if (dev->bus && dev->bus->remove)
+ dev->bus->remove(dev);
+ else if (dev->driver->remove)
+ dev->driver->remove(dev);
+ else
+ return false; /* nothing to do */
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(device_remove);
+
static void devices_shutdown(void)
{
- struct device_d *dev;
+ struct device *dev;
- list_for_each_entry(dev, &active, active) {
- if (dev->bus->remove)
- dev->bus->remove(dev);
+ list_for_each_entry(dev, &active_device_list, active) {
+ if (device_remove(dev))
+ pr_report_probe("%*sremove-> %s\n", 1 * 4, "", dev_name(dev));
+ dev->driver = NULL;
}
}
devshutdown_exitcall(devices_shutdown);
-int dev_get_drvdata(struct device_d *dev, const void **data)
+int dev_get_drvdata(struct device *dev, const void **data)
{
if (dev->of_id_entry) {
*data = dev->of_id_entry->data;
@@ -498,3 +712,115 @@ int dev_get_drvdata(struct device_d *dev, const void **data)
return -ENODEV;
}
+
+const void *device_get_match_data(struct device *dev)
+{
+ if (dev->of_id_entry)
+ return dev->of_id_entry->data;
+
+ if (dev->id_entry)
+ return (void *)dev->id_entry->driver_data;
+
+ return NULL;
+}
+
+static void device_set_deferred_probe_reason(struct device *dev,
+ const struct va_format *vaf)
+{
+ char *reason;
+ char *last_char;
+
+ free(dev->deferred_probe_reason);
+
+ reason = xasprintf("%pV", vaf);
+
+ /* drop newline char at end of reason string */
+ last_char = reason + strlen(reason) - 1;
+
+ if (*last_char == '\n')
+ *last_char = '\0';
+
+ dev->deferred_probe_reason = reason;
+}
+
+/**
+ * dev_err_probe - probe error check and log helper
+ * @loglevel: log level configured in source file
+ * @dev: the pointer to the struct device
+ * @err: error value to test
+ * @fmt: printf-style format string
+ * @...: arguments as specified in the format string
+ *
+ * This helper implements common pattern present in probe functions for error
+ * checking: print debug or error message depending if the error value is
+ * -EPROBE_DEFER and propagate error upwards.
+ *
+ * In case of -EPROBE_DEFER it sets the device's deferred_probe_reason attribute,
+ * but does not report an error. The error is recorded and displayed later, if
+ * (and only if) the probe is permanently deferred. For all other error codes,
+ * it just outputs the error along with the formatted message.
+ *
+ * It replaces code sequence::
+ *
+ * if (err != -EPROBE_DEFER)
+ * dev_err(dev, ...);
+ * else
+ * dev_dbg(dev, ...);
+ * return err;
+ *
+ * with::
+ *
+ * return dev_err_probe(dev, err, ...);
+ *
+ * Returns @err.
+ *
+ */
+int dev_err_probe(struct device *dev, int err, const char *fmt, ...);
+int dev_err_probe(struct device *dev, int err, const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, fmt);
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ if (err == -EPROBE_DEFER)
+ device_set_deferred_probe_reason(dev, &vaf);
+
+ dev_printf(err == -EPROBE_DEFER ? MSG_DEBUG : MSG_ERR,
+ dev, "error %pe: %pV", ERR_PTR(err), &vaf);
+
+ va_end(args);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(dev_err_probe);
+
+/*
+ * device_find_child - device iterator for locating a particular device.
+ * @parent: parent struct device
+ * @match: Callback function to check device
+ * @data: Data to pass to match function
+ *
+ * The callback should return 0 if the device doesn't match and non-zero
+ * if it does. If the callback returns non-zero and a reference to the
+ * current device can be obtained, this function will return to the caller
+ * and not iterate over any more devices.
+ */
+struct device *device_find_child(struct device *parent, void *data,
+ int (*match)(struct device *dev, void *data))
+{
+ struct device *child;
+
+ if (!parent)
+ return NULL;
+
+ list_for_each_entry(child, &parent->children, sibling) {
+ if (match(child, data))
+ return child;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(device_find_child);