| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (c) 2018 JJ Hiblot <jjhiblot@ti.com> |
| */ |
| |
| #include <common.h> |
| #include <command.h> |
| #include <dm.h> |
| #include <dm/device-internal.h> |
| #include <dm/lists.h> |
| #include <dm/root.h> |
| #include <dm/uclass-internal.h> |
| |
| static int bind_by_class_index(const char *uclass, int index, |
| const char *drv_name) |
| { |
| static enum uclass_id uclass_id; |
| struct udevice *dev; |
| struct udevice *parent; |
| int ret; |
| struct driver *drv; |
| |
| drv = lists_driver_lookup_name(drv_name); |
| if (!drv) { |
| printf("Cannot find driver '%s'\n", drv_name); |
| return -ENOENT; |
| } |
| |
| uclass_id = uclass_get_by_name(uclass); |
| if (uclass_id == UCLASS_INVALID) { |
| printf("%s is not a valid uclass\n", uclass); |
| return -EINVAL; |
| } |
| |
| ret = uclass_find_device(uclass_id, index, &parent); |
| if (!parent || ret) { |
| printf("Cannot find device %d of class %s\n", index, uclass); |
| return ret; |
| } |
| |
| ret = device_bind_with_driver_data(parent, drv, drv->name, 0, |
| ofnode_null(), &dev); |
| if (!dev || ret) { |
| printf("Unable to bind. err:%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int find_dev(const char *uclass, int index, struct udevice **devp) |
| { |
| static enum uclass_id uclass_id; |
| int rc; |
| |
| uclass_id = uclass_get_by_name(uclass); |
| if (uclass_id == UCLASS_INVALID) { |
| printf("%s is not a valid uclass\n", uclass); |
| return -EINVAL; |
| } |
| |
| rc = uclass_find_device(uclass_id, index, devp); |
| if (!*devp || rc) { |
| printf("Cannot find device %d of class %s\n", index, uclass); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int unbind_by_class_index(const char *uclass, int index) |
| { |
| int ret; |
| struct udevice *dev; |
| |
| ret = find_dev(uclass, index, &dev); |
| if (ret) |
| return ret; |
| |
| ret = device_remove(dev, DM_REMOVE_NORMAL); |
| if (ret) { |
| printf("Unable to remove. err:%d\n", ret); |
| return ret; |
| } |
| |
| ret = device_unbind(dev); |
| if (ret) { |
| printf("Unable to unbind. err:%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int unbind_child_by_class_index(const char *uclass, int index, |
| const char *drv_name) |
| { |
| struct udevice *parent; |
| int ret; |
| struct driver *drv; |
| |
| drv = lists_driver_lookup_name(drv_name); |
| if (!drv) { |
| printf("Cannot find driver '%s'\n", drv_name); |
| return -ENOENT; |
| } |
| |
| ret = find_dev(uclass, index, &parent); |
| if (ret) |
| return ret; |
| |
| ret = device_chld_remove(parent, drv, DM_REMOVE_NORMAL); |
| if (ret) |
| printf("Unable to remove all. err:%d\n", ret); |
| |
| ret = device_chld_unbind(parent, drv); |
| if (ret) |
| printf("Unable to unbind all. err:%d\n", ret); |
| |
| return ret; |
| } |
| |
| static int bind_by_node_path(const char *path, const char *drv_name) |
| { |
| struct udevice *dev; |
| struct udevice *parent = NULL; |
| int ret; |
| ofnode ofnode; |
| struct driver *drv; |
| |
| drv = lists_driver_lookup_name(drv_name); |
| if (!drv) { |
| printf("%s is not a valid driver name\n", drv_name); |
| return -ENOENT; |
| } |
| |
| ofnode = ofnode_path(path); |
| if (!ofnode_valid(ofnode)) { |
| printf("%s is not a valid node path\n", path); |
| return -EINVAL; |
| } |
| |
| while (ofnode_valid(ofnode)) { |
| if (!device_find_global_by_ofnode(ofnode, &parent)) |
| break; |
| ofnode = ofnode_get_parent(ofnode); |
| } |
| |
| if (!parent) { |
| printf("Cannot find a parent device for node path %s\n", path); |
| return -ENODEV; |
| } |
| |
| ofnode = ofnode_path(path); |
| ret = lists_bind_fdt(parent, ofnode, &dev, drv, false); |
| |
| if (!dev || ret) { |
| printf("Unable to bind. err:%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int unbind_by_node_path(const char *path) |
| { |
| struct udevice *dev; |
| int ret; |
| ofnode ofnode; |
| |
| ofnode = ofnode_path(path); |
| if (!ofnode_valid(ofnode)) { |
| printf("%s is not a valid node path\n", path); |
| return -EINVAL; |
| } |
| |
| ret = device_find_global_by_ofnode(ofnode, &dev); |
| |
| if (!dev || ret) { |
| printf("Cannot find a device with path %s\n", path); |
| return -ENODEV; |
| } |
| |
| ret = device_remove(dev, DM_REMOVE_NORMAL); |
| if (ret) { |
| printf("Unable to remove. err:%d\n", ret); |
| return ret; |
| } |
| |
| ret = device_unbind(dev); |
| if (ret) { |
| printf("Unable to unbind. err:%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int do_bind_unbind(struct cmd_tbl *cmdtp, int flag, int argc, |
| char *const argv[]) |
| { |
| int ret = 0; |
| bool bind; |
| bool by_node; |
| |
| if (argc < 2) |
| return CMD_RET_USAGE; |
| |
| bind = (argv[0][0] == 'b'); |
| by_node = (argv[1][0] == '/'); |
| |
| if (by_node && bind) { |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| ret = bind_by_node_path(argv[1], argv[2]); |
| } else if (by_node && !bind) { |
| if (argc != 2) |
| return CMD_RET_USAGE; |
| ret = unbind_by_node_path(argv[1]); |
| } else if (!by_node && bind) { |
| int index = (argc > 2) ? dectoul(argv[2], NULL) : 0; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| ret = bind_by_class_index(argv[1], index, argv[3]); |
| } else if (!by_node && !bind) { |
| int index = (argc > 2) ? dectoul(argv[2], NULL) : 0; |
| |
| if (argc == 3) |
| ret = unbind_by_class_index(argv[1], index); |
| else if (argc == 4) |
| ret = unbind_child_by_class_index(argv[1], index, |
| argv[3]); |
| else |
| return CMD_RET_USAGE; |
| } |
| |
| if (ret) |
| return CMD_RET_FAILURE; |
| else |
| return CMD_RET_SUCCESS; |
| } |
| |
| U_BOOT_CMD( |
| bind, 4, 0, do_bind_unbind, |
| "Bind a device to a driver", |
| "<node path> <driver>\n" |
| "bind <class> <index> <driver>\n" |
| ); |
| |
| U_BOOT_CMD( |
| unbind, 4, 0, do_bind_unbind, |
| "Unbind a device from a driver", |
| "<node path>\n" |
| "unbind <class> <index>\n" |
| "unbind <class> <index> <driver>\n" |
| ); |