u-boot-brain/drivers/usb/host/ehci-generic.c
Patrice Chotard a1cee8e808 usb: host: ehci-generic: add error path and .remove callback
Use an array to save enabled clocks reference and deasserted resets
in order to respectively disabled and asserted them in case of error
during probe() or during driver removal.

Signed-off-by: Patrice Chotard <patrice.chotard@st.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
2017-07-28 23:34:09 +02:00

147 lines
3.2 KiB
C

/*
* Copyright (C) 2015 Alexey Brodkin <abrodkin@synopsys.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <clk.h>
#include <dm/ofnode.h>
#include <reset.h>
#include <asm/io.h>
#include <dm.h>
#include "ehci.h"
/*
* Even though here we don't explicitly use "struct ehci_ctrl"
* ehci_register() expects it to be the first thing that resides in
* device's private data.
*/
struct generic_ehci {
struct ehci_ctrl ctrl;
struct clk *clocks;
struct reset_ctl *resets;
int clock_count;
int reset_count;
};
static int ehci_usb_probe(struct udevice *dev)
{
struct generic_ehci *priv = dev_get_priv(dev);
struct ehci_hccr *hccr;
struct ehci_hcor *hcor;
int i, err, ret, clock_nb, reset_nb;
err = 0;
priv->clock_count = 0;
clock_nb = ofnode_count_phandle_with_args(dev_ofnode(dev), "clocks",
"#clock-cells");
if (clock_nb > 0) {
priv->clocks = devm_kcalloc(dev, clock_nb, sizeof(struct clk),
GFP_KERNEL);
if (!priv->clocks)
return -ENOMEM;
for (i = 0; i < clock_nb; i++) {
err = clk_get_by_index(dev, i, &priv->clocks[i]);
if (err < 0)
break;
err = clk_enable(&priv->clocks[i]);
if (err) {
error("failed to enable clock %d\n", i);
clk_free(&priv->clocks[i]);
goto clk_err;
}
priv->clock_count++;
}
} else {
if (clock_nb != -ENOENT) {
error("failed to get clock phandle(%d)\n", clock_nb);
return clock_nb;
}
}
priv->reset_count = 0;
reset_nb = ofnode_count_phandle_with_args(dev_ofnode(dev), "resets",
"#reset-cells");
if (reset_nb > 0) {
priv->resets = devm_kcalloc(dev, reset_nb,
sizeof(struct reset_ctl),
GFP_KERNEL);
if (!priv->resets)
return -ENOMEM;
for (i = 0; i < reset_nb; i++) {
err = reset_get_by_index(dev, i, &priv->resets[i]);
if (err < 0)
break;
if (reset_deassert(&priv->resets[i])) {
error("failed to deassert reset %d\n", i);
reset_free(&priv->resets[i]);
goto reset_err;
}
priv->reset_count++;
}
} else {
if (reset_nb != -ENOENT) {
error("failed to get reset phandle(%d)\n", reset_nb);
goto clk_err;
}
}
hccr = map_physmem(devfdt_get_addr(dev), 0x100, MAP_NOCACHE);
hcor = (struct ehci_hcor *)((uintptr_t)hccr +
HC_LENGTH(ehci_readl(&hccr->cr_capbase)));
err = ehci_register(dev, hccr, hcor, NULL, 0, USB_INIT_HOST);
if (err)
goto reset_err;
return 0;
reset_err:
ret = reset_release_all(priv->resets, priv->reset_count);
if (ret)
error("failed to assert all resets\n");
clk_err:
ret = clk_release_all(priv->clocks, priv->clock_count);
if (ret)
error("failed to disable all clocks\n");
return err;
}
static int ehci_usb_remove(struct udevice *dev)
{
struct generic_ehci *priv = dev_get_priv(dev);
int ret;
ret = ehci_deregister(dev);
if (ret)
return ret;
ret = reset_release_all(priv->resets, priv->reset_count);
if (ret)
return ret;
return clk_release_all(priv->clocks, priv->clock_count);
}
static const struct udevice_id ehci_usb_ids[] = {
{ .compatible = "generic-ehci" },
{ }
};
U_BOOT_DRIVER(ehci_generic) = {
.name = "ehci_generic",
.id = UCLASS_USB,
.of_match = ehci_usb_ids,
.probe = ehci_usb_probe,
.remove = ehci_usb_remove,
.ops = &ehci_usb_ops,
.priv_auto_alloc_size = sizeof(struct generic_ehci),
.flags = DM_FLAG_ALLOC_PRIV_DMA,
};