Files
u-boot-krane/drivers/core/lists.c
T
Markus Schneider-Pargmann (TI.com)andTom Rini 6668b8e7cc dm: core: Support multiple drivers with same compatibles
Currently once a driver matched the compatible string of a device, other
drivers are ignored. If the first matching driver returns -ENODEV, no
other possibly matching drivers are iterated with that compatible of the
device. Instead the next compatible in the list of compatibles is
selected, assuming only one driver matches one compatible at a time.

To be able to use the bind function to return -ENODEV and continue
matching other drivers with the same compatible, move the for loop a bit
to continue the for loop after -ENODEV was returned. The loop had to be
adjusted a bit to still support the 'drv' argument properly. Some
simplifications were done as well.

The modification will only add additional loop iterations if -ENODEV is
returned. Otherwise the exit and continue conditions for the loop stay
the same and do not cause any additional iterations and should not
impact performance.

This is required for ti-musb-host and ti-musb-peripheral which both
match on the same device but differ based on the dr_mode DT property.
Depending on this property, the driver is either UCLASS_USB or
UCLASS_USB_GADGET_GENERIC. By checking the DT property in the bind
function and returning -ENODEV the other driver can probe instead.

Reviewed-by: Simon Glass <simon.glass@canonical.com>
Acked-by: Dinesh Maniyam <dinesh.maniyam@altera.com>
Signed-off-by: Markus Schneider-Pargmann (TI.com) <msp@baylibre.com>
2026-01-28 09:20:59 -06:00

291 lines
7.2 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2013 Google, Inc
*
* (C) Copyright 2012
* Marek Vasut <marex@denx.de>
*/
#define LOG_CATEGORY LOGC_DM
#include <debug_uart.h>
#include <errno.h>
#include <log.h>
#include <dm/device.h>
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <dm/platdata.h>
#include <dm/uclass.h>
#include <dm/util.h>
#include <fdtdec.h>
#include <linux/compiler.h>
struct driver *lists_driver_lookup_name(const char *name)
{
struct driver *drv =
ll_entry_start(struct driver, driver);
const int n_ents = ll_entry_count(struct driver, driver);
struct driver *entry;
for (entry = drv; entry != drv + n_ents; entry++) {
if (!strcmp(name, entry->name))
return entry;
}
/* Not found */
return NULL;
}
struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
{
struct uclass_driver *uclass =
ll_entry_start(struct uclass_driver, uclass_driver);
const int n_ents = ll_entry_count(struct uclass_driver, uclass_driver);
struct uclass_driver *entry;
for (entry = uclass; entry != uclass + n_ents; entry++) {
if (entry->id == id)
return entry;
}
return NULL;
}
/**
* bind_drivers_pass() - Perform a pass of driver binding
*
* Work through the driver_info records binding a driver for each one. If the
* binding fails, continue binding others, but return the error.
*
* For OF_PLATDATA we must bind parent devices before their children. So only
* children of bound parents are bound on each call to this function. When a
* child is left unbound, -EAGAIN is returned, indicating that this function
* should be called again
*
* @parent: Parent device to use when binding each child device
* Return: 0 if OK, -EAGAIN if unbound children exist, -ENOENT if there is no
* driver for one of the devices, other -ve on other error
*/
static int bind_drivers_pass(struct udevice *parent, bool pre_reloc_only)
{
struct driver_info *info =
ll_entry_start(struct driver_info, driver_info);
const int n_ents = ll_entry_count(struct driver_info, driver_info);
bool missing_parent = false;
int result = 0;
int idx;
/*
* Do one iteration through the driver_info records. For of-platdata,
* bind only devices whose parent is already bound. If we find any
* device we can't bind, set missing_parent to true, which will cause
* this function to be called again.
*/
for (idx = 0; idx < n_ents; idx++) {
struct udevice *par = parent;
const struct driver_info *entry = info + idx;
struct driver_rt *drt = gd_dm_driver_rt() + idx;
struct udevice *dev;
int ret;
if (CONFIG_IS_ENABLED(OF_PLATDATA)) {
int parent_idx = driver_info_parent_id(entry);
if (drt->dev)
continue;
if (CONFIG_IS_ENABLED(OF_PLATDATA_PARENT) &&
parent_idx != -1) {
struct driver_rt *parent_drt;
parent_drt = gd_dm_driver_rt() + parent_idx;
if (!parent_drt->dev) {
missing_parent = true;
continue;
}
par = parent_drt->dev;
}
}
ret = device_bind_by_name(par, pre_reloc_only, entry, &dev);
if (!ret) {
if (CONFIG_IS_ENABLED(OF_PLATDATA))
drt->dev = dev;
} else if (ret != -EPERM) {
dm_warn("No match for driver '%s'\n", entry->name);
if (!result || ret != -ENOENT)
result = ret;
}
}
return result ? result : missing_parent ? -EAGAIN : 0;
}
int lists_bind_drivers(struct udevice *parent, bool pre_reloc_only)
{
int result = 0;
int pass;
/*
* 10 passes is 10 levels deep in the devicetree, which is plenty. If
* OF_PLATDATA_PARENT is not enabled, then bind_drivers_pass() will
* always succeed on the first pass.
*/
for (pass = 0; pass < 10; pass++) {
int ret;
ret = bind_drivers_pass(parent, pre_reloc_only);
if (!result || result == -EAGAIN)
result = ret;
if (ret != -EAGAIN)
break;
}
return result;
}
int device_bind_driver(struct udevice *parent, const char *drv_name,
const char *dev_name, struct udevice **devp)
{
return device_bind_driver_to_node(parent, drv_name, dev_name,
ofnode_null(), devp);
}
int device_bind_driver_to_node(struct udevice *parent, const char *drv_name,
const char *dev_name, ofnode node,
struct udevice **devp)
{
struct driver *drv;
int ret;
drv = lists_driver_lookup_name(drv_name);
if (!drv) {
dm_warn("Cannot find driver '%s'\n", drv_name);
return -ENOENT;
}
ret = device_bind_with_driver_data(parent, drv, dev_name, 0 /* data */,
node, devp);
return ret;
}
#if CONFIG_IS_ENABLED(OF_REAL)
/**
* driver_check_compatible() - Check if a driver matches a compatible string
*
* @param of_match: List of compatible strings to match
* @param of_idp: Returns the match that was found
* @param compat: The compatible string to search for
* Return: 0 if there is a match, -ENOENT if no match
*/
static int driver_check_compatible(const struct udevice_id *of_match,
const struct udevice_id **of_idp,
const char *compat)
{
if (!of_match)
return -ENOENT;
while (of_match->compatible) {
if (!strcmp(of_match->compatible, compat)) {
*of_idp = of_match;
return 0;
}
of_match++;
}
return -ENOENT;
}
int lists_bind_fdt(struct udevice *parent, ofnode node, struct udevice **devp,
struct driver *drv, bool pre_reloc_only)
{
struct driver *driver = ll_entry_start(struct driver, driver);
const int n_ents = ll_entry_count(struct driver, driver);
const struct udevice_id *id;
struct driver *entry;
struct udevice *dev;
const char *name, *compat_list, *compat;
int compat_length, i;
int ret = 0;
if (devp)
*devp = NULL;
name = ofnode_get_name(node);
log_debug("bind node %s\n", name);
compat_list = ofnode_get_property(node, "compatible", &compat_length);
if (!compat_list) {
if (compat_length == -FDT_ERR_NOTFOUND) {
log_debug("Device '%s' has no compatible string\n",
name);
return 0;
}
dm_warn("Device tree error at node '%s'\n", name);
return compat_length;
}
/*
* Walk through the compatible string list, attempting to match each
* compatible string in order such that we match in order of priority
* from the first string to the last.
*/
for (i = 0; i < compat_length; i += strlen(compat) + 1) {
compat = compat_list + i;
log_debug(" - attempt to match compatible string '%s'\n",
compat);
for (entry = driver; entry != driver + n_ents; entry++) {
/* Search for drivers with matching drv or existing of_match */
if (drv) {
if (drv != entry)
continue;
} else if (!entry->of_match) {
continue;
}
id = NULL;
if (entry->of_match) {
ret = driver_check_compatible(entry->of_match, &id,
compat);
if (ret)
continue;
log_debug(" - found match at driver '%s' for '%s'\n",
entry->name, id->compatible);
}
if (pre_reloc_only) {
if (!ofnode_pre_reloc(node) &&
!(entry->flags & DM_FLAG_PRE_RELOC)) {
log_debug("Skipping device pre-relocation\n");
return 0;
}
}
ret = device_bind_with_driver_data(parent, entry, name,
id ? id->data : 0, node,
&dev);
if (!drv && ret == -ENODEV) {
log_debug("Driver '%s' refuses to bind\n", entry->name);
continue;
}
if (ret) {
dm_warn("Error binding driver '%s': %d\n", entry->name,
ret);
return log_msg_ret("bind", ret);
}
if (devp)
*devp = dev;
return 0;
}
}
if (ret != -ENODEV)
log_debug("No match for node '%s'\n", name);
return 0;
}
#endif