| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2016, NVIDIA CORPORATION. |
| */ |
| |
| #define LOG_CATEGORY UCLASS_RESET |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <fdtdec.h> |
| #include <log.h> |
| #include <malloc.h> |
| #include <reset.h> |
| #include <reset-uclass.h> |
| #include <dm/devres.h> |
| #include <dm/lists.h> |
| |
| static inline struct reset_ops *reset_dev_ops(struct udevice *dev) |
| { |
| return (struct reset_ops *)dev->driver->ops; |
| } |
| |
| static int reset_of_xlate_default(struct reset_ctl *reset_ctl, |
| struct ofnode_phandle_args *args) |
| { |
| debug("%s(reset_ctl=%p)\n", __func__, reset_ctl); |
| |
| if (args->args_count != 1) { |
| debug("Invaild args_count: %d\n", args->args_count); |
| return -EINVAL; |
| } |
| |
| reset_ctl->id = args->args[0]; |
| |
| return 0; |
| } |
| |
| static int reset_get_by_index_tail(int ret, ofnode node, |
| struct ofnode_phandle_args *args, |
| const char *list_name, int index, |
| struct reset_ctl *reset_ctl) |
| { |
| struct udevice *dev_reset; |
| struct reset_ops *ops; |
| |
| assert(reset_ctl); |
| reset_ctl->dev = NULL; |
| if (ret) |
| return ret; |
| |
| ret = uclass_get_device_by_ofnode(UCLASS_RESET, args->node, |
| &dev_reset); |
| if (ret) { |
| debug("%s: uclass_get_device_by_ofnode() failed: %d\n", |
| __func__, ret); |
| debug("%s %d\n", ofnode_get_name(args->node), args->args[0]); |
| return ret; |
| } |
| ops = reset_dev_ops(dev_reset); |
| |
| reset_ctl->dev = dev_reset; |
| if (ops->of_xlate) |
| ret = ops->of_xlate(reset_ctl, args); |
| else |
| ret = reset_of_xlate_default(reset_ctl, args); |
| if (ret) { |
| debug("of_xlate() failed: %d\n", ret); |
| return ret; |
| } |
| |
| ret = ops->request(reset_ctl); |
| if (ret) { |
| debug("ops->request() failed: %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int reset_get_by_index(struct udevice *dev, int index, |
| struct reset_ctl *reset_ctl) |
| { |
| struct ofnode_phandle_args args; |
| int ret; |
| |
| ret = dev_read_phandle_with_args(dev, "resets", "#reset-cells", 0, |
| index, &args); |
| |
| return reset_get_by_index_tail(ret, dev_ofnode(dev), &args, "resets", |
| index > 0, reset_ctl); |
| } |
| |
| int reset_get_by_index_nodev(ofnode node, int index, |
| struct reset_ctl *reset_ctl) |
| { |
| struct ofnode_phandle_args args; |
| int ret; |
| |
| ret = ofnode_parse_phandle_with_args(node, "resets", "#reset-cells", 0, |
| index, &args); |
| |
| return reset_get_by_index_tail(ret, node, &args, "resets", |
| index > 0, reset_ctl); |
| } |
| |
| static int __reset_get_bulk(struct udevice *dev, ofnode node, |
| struct reset_ctl_bulk *bulk) |
| { |
| int i, ret, err, count; |
| |
| bulk->count = 0; |
| |
| count = ofnode_count_phandle_with_args(node, "resets", "#reset-cells", |
| 0); |
| if (count < 1) |
| return count; |
| |
| bulk->resets = devm_kcalloc(dev, count, sizeof(struct reset_ctl), |
| GFP_KERNEL); |
| if (!bulk->resets) |
| return -ENOMEM; |
| |
| for (i = 0; i < count; i++) { |
| ret = reset_get_by_index_nodev(node, i, &bulk->resets[i]); |
| if (ret < 0) |
| goto bulk_get_err; |
| |
| ++bulk->count; |
| } |
| |
| return 0; |
| |
| bulk_get_err: |
| err = reset_release_all(bulk->resets, bulk->count); |
| if (err) |
| debug("%s: could release all resets for %p\n", |
| __func__, dev); |
| |
| return ret; |
| } |
| |
| int reset_get_bulk(struct udevice *dev, struct reset_ctl_bulk *bulk) |
| { |
| return __reset_get_bulk(dev, dev_ofnode(dev), bulk); |
| } |
| |
| int reset_get_by_name(struct udevice *dev, const char *name, |
| struct reset_ctl *reset_ctl) |
| { |
| int index; |
| |
| debug("%s(dev=%p, name=%s, reset_ctl=%p)\n", __func__, dev, name, |
| reset_ctl); |
| reset_ctl->dev = NULL; |
| |
| index = dev_read_stringlist_search(dev, "reset-names", name); |
| if (index < 0) { |
| debug("fdt_stringlist_search() failed: %d\n", index); |
| return index; |
| } |
| |
| return reset_get_by_index(dev, index, reset_ctl); |
| } |
| |
| int reset_request(struct reset_ctl *reset_ctl) |
| { |
| struct reset_ops *ops = reset_dev_ops(reset_ctl->dev); |
| |
| debug("%s(reset_ctl=%p)\n", __func__, reset_ctl); |
| |
| return ops->request(reset_ctl); |
| } |
| |
| int reset_free(struct reset_ctl *reset_ctl) |
| { |
| struct reset_ops *ops = reset_dev_ops(reset_ctl->dev); |
| |
| debug("%s(reset_ctl=%p)\n", __func__, reset_ctl); |
| |
| return ops->rfree(reset_ctl); |
| } |
| |
| int reset_assert(struct reset_ctl *reset_ctl) |
| { |
| struct reset_ops *ops = reset_dev_ops(reset_ctl->dev); |
| |
| debug("%s(reset_ctl=%p)\n", __func__, reset_ctl); |
| |
| return ops->rst_assert(reset_ctl); |
| } |
| |
| int reset_assert_bulk(struct reset_ctl_bulk *bulk) |
| { |
| int i, ret; |
| |
| for (i = 0; i < bulk->count; i++) { |
| ret = reset_assert(&bulk->resets[i]); |
| if (ret < 0) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int reset_deassert(struct reset_ctl *reset_ctl) |
| { |
| struct reset_ops *ops = reset_dev_ops(reset_ctl->dev); |
| |
| debug("%s(reset_ctl=%p)\n", __func__, reset_ctl); |
| |
| return ops->rst_deassert(reset_ctl); |
| } |
| |
| int reset_deassert_bulk(struct reset_ctl_bulk *bulk) |
| { |
| int i, ret; |
| |
| for (i = 0; i < bulk->count; i++) { |
| ret = reset_deassert(&bulk->resets[i]); |
| if (ret < 0) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int reset_status(struct reset_ctl *reset_ctl) |
| { |
| struct reset_ops *ops = reset_dev_ops(reset_ctl->dev); |
| |
| debug("%s(reset_ctl=%p)\n", __func__, reset_ctl); |
| |
| return ops->rst_status(reset_ctl); |
| } |
| |
| int reset_release_all(struct reset_ctl *reset_ctl, int count) |
| { |
| int i, ret; |
| |
| for (i = 0; i < count; i++) { |
| debug("%s(reset_ctl[%d]=%p)\n", __func__, i, &reset_ctl[i]); |
| |
| /* check if reset has been previously requested */ |
| if (!reset_ctl[i].dev) |
| continue; |
| |
| ret = reset_assert(&reset_ctl[i]); |
| if (ret) |
| return ret; |
| |
| ret = reset_free(&reset_ctl[i]); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static void devm_reset_release(struct udevice *dev, void *res) |
| { |
| reset_free(res); |
| } |
| |
| struct reset_ctl *devm_reset_control_get_by_index(struct udevice *dev, |
| int index) |
| { |
| int rc; |
| struct reset_ctl *reset_ctl; |
| |
| reset_ctl = devres_alloc(devm_reset_release, sizeof(struct reset_ctl), |
| __GFP_ZERO); |
| if (unlikely(!reset_ctl)) |
| return ERR_PTR(-ENOMEM); |
| |
| rc = reset_get_by_index(dev, index, reset_ctl); |
| if (rc) |
| return ERR_PTR(rc); |
| |
| devres_add(dev, reset_ctl); |
| return reset_ctl; |
| } |
| |
| struct reset_ctl *devm_reset_control_get(struct udevice *dev, const char *id) |
| { |
| int rc; |
| struct reset_ctl *reset_ctl; |
| |
| reset_ctl = devres_alloc(devm_reset_release, sizeof(struct reset_ctl), |
| __GFP_ZERO); |
| if (unlikely(!reset_ctl)) |
| return ERR_PTR(-ENOMEM); |
| |
| rc = reset_get_by_name(dev, id, reset_ctl); |
| if (rc) |
| return ERR_PTR(rc); |
| |
| devres_add(dev, reset_ctl); |
| return reset_ctl; |
| } |
| |
| struct reset_ctl *devm_reset_control_get_optional(struct udevice *dev, |
| const char *id) |
| { |
| struct reset_ctl *r = devm_reset_control_get(dev, id); |
| |
| if (IS_ERR(r)) |
| return NULL; |
| |
| return r; |
| } |
| |
| static void devm_reset_bulk_release(struct udevice *dev, void *res) |
| { |
| struct reset_ctl_bulk *bulk = res; |
| |
| reset_release_all(bulk->resets, bulk->count); |
| } |
| |
| struct reset_ctl_bulk *devm_reset_bulk_get_by_node(struct udevice *dev, |
| ofnode node) |
| { |
| int rc; |
| struct reset_ctl_bulk *bulk; |
| |
| bulk = devres_alloc(devm_reset_bulk_release, |
| sizeof(struct reset_ctl_bulk), |
| __GFP_ZERO); |
| |
| /* this looks like a leak, but devres takes care of it */ |
| if (unlikely(!bulk)) |
| return ERR_PTR(-ENOMEM); |
| |
| rc = __reset_get_bulk(dev, node, bulk); |
| if (rc) |
| return ERR_PTR(rc); |
| |
| devres_add(dev, bulk); |
| return bulk; |
| } |
| |
| struct reset_ctl_bulk *devm_reset_bulk_get_optional_by_node(struct udevice *dev, |
| ofnode node) |
| { |
| struct reset_ctl_bulk *bulk; |
| |
| bulk = devm_reset_bulk_get_by_node(dev, node); |
| |
| if (IS_ERR(bulk)) |
| return NULL; |
| |
| return bulk; |
| } |
| |
| struct reset_ctl_bulk *devm_reset_bulk_get(struct udevice *dev) |
| { |
| return devm_reset_bulk_get_by_node(dev, dev_ofnode(dev)); |
| } |
| |
| struct reset_ctl_bulk *devm_reset_bulk_get_optional(struct udevice *dev) |
| { |
| return devm_reset_bulk_get_optional_by_node(dev, dev_ofnode(dev)); |
| } |
| |
| UCLASS_DRIVER(reset) = { |
| .id = UCLASS_RESET, |
| .name = "reset", |
| }; |