mirror of
https://github.com/torvalds/linux.git
synced 2024-12-02 09:01:34 +00:00
8e593a223e
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is (mostly) ignored and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new() which already returns void. Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be> Link: https://lore.kernel.org/r/20230307115900.2293120-18-u.kleine-koenig@pengutronix.de Signed-off-by: Vinod Koul <vkoul@kernel.org>
220 lines
5.3 KiB
C
220 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Renesas R-Car Gen3 for USB3.0 PHY driver
|
|
*
|
|
* Copyright (C) 2017 Renesas Electronics Corporation
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#define USB30_CLKSET0 0x034
|
|
#define USB30_CLKSET1 0x036
|
|
#define USB30_SSC_SET 0x038
|
|
#define USB30_PHY_ENABLE 0x060
|
|
#define USB30_VBUS_EN 0x064
|
|
|
|
/* USB30_CLKSET0 */
|
|
#define CLKSET0_PRIVATE 0x05c0
|
|
#define CLKSET0_USB30_FSEL_USB_EXTAL 0x0002
|
|
|
|
/* USB30_CLKSET1 */
|
|
#define CLKSET1_USB30_PLL_MULTI_SHIFT 6
|
|
#define CLKSET1_USB30_PLL_MULTI_USB_EXTAL (0x64 << \
|
|
CLKSET1_USB30_PLL_MULTI_SHIFT)
|
|
#define CLKSET1_PHYRESET BIT(4) /* 1: reset */
|
|
#define CLKSET1_REF_CLKDIV BIT(3) /* 1: USB_EXTAL */
|
|
#define CLKSET1_PRIVATE_2_1 BIT(1) /* Write B'01 */
|
|
#define CLKSET1_REF_CLK_SEL BIT(0) /* 1: USB3S0_CLK_P */
|
|
|
|
/* USB30_SSC_SET */
|
|
#define SSC_SET_SSC_EN BIT(12)
|
|
#define SSC_SET_RANGE_SHIFT 9
|
|
#define SSC_SET_RANGE_4980 (0x0 << SSC_SET_RANGE_SHIFT)
|
|
#define SSC_SET_RANGE_4492 (0x1 << SSC_SET_RANGE_SHIFT)
|
|
#define SSC_SET_RANGE_4003 (0x2 << SSC_SET_RANGE_SHIFT)
|
|
|
|
/* USB30_PHY_ENABLE */
|
|
#define PHY_ENABLE_RESET_EN BIT(4)
|
|
|
|
/* USB30_VBUS_EN */
|
|
#define VBUS_EN_VBUS_EN BIT(1)
|
|
|
|
struct rcar_gen3_usb3 {
|
|
void __iomem *base;
|
|
struct phy *phy;
|
|
u32 ssc_range;
|
|
bool usb3s_clk;
|
|
bool usb_extal;
|
|
};
|
|
|
|
static void write_clkset1_for_usb_extal(struct rcar_gen3_usb3 *r, bool reset)
|
|
{
|
|
u16 val = CLKSET1_USB30_PLL_MULTI_USB_EXTAL |
|
|
CLKSET1_REF_CLKDIV | CLKSET1_PRIVATE_2_1;
|
|
|
|
if (reset)
|
|
val |= CLKSET1_PHYRESET;
|
|
|
|
writew(val, r->base + USB30_CLKSET1);
|
|
}
|
|
|
|
static void rcar_gen3_phy_usb3_enable_ssc(struct rcar_gen3_usb3 *r)
|
|
{
|
|
u16 val = SSC_SET_SSC_EN;
|
|
|
|
switch (r->ssc_range) {
|
|
case 4980:
|
|
val |= SSC_SET_RANGE_4980;
|
|
break;
|
|
case 4492:
|
|
val |= SSC_SET_RANGE_4492;
|
|
break;
|
|
case 4003:
|
|
val |= SSC_SET_RANGE_4003;
|
|
break;
|
|
default:
|
|
dev_err(&r->phy->dev, "%s: unsupported range (%x)\n", __func__,
|
|
r->ssc_range);
|
|
return;
|
|
}
|
|
|
|
writew(val, r->base + USB30_SSC_SET);
|
|
}
|
|
|
|
static void rcar_gen3_phy_usb3_select_usb_extal(struct rcar_gen3_usb3 *r)
|
|
{
|
|
write_clkset1_for_usb_extal(r, false);
|
|
if (r->ssc_range)
|
|
rcar_gen3_phy_usb3_enable_ssc(r);
|
|
writew(CLKSET0_PRIVATE | CLKSET0_USB30_FSEL_USB_EXTAL,
|
|
r->base + USB30_CLKSET0);
|
|
writew(PHY_ENABLE_RESET_EN, r->base + USB30_PHY_ENABLE);
|
|
write_clkset1_for_usb_extal(r, true);
|
|
usleep_range(10, 20);
|
|
write_clkset1_for_usb_extal(r, false);
|
|
}
|
|
|
|
static int rcar_gen3_phy_usb3_init(struct phy *p)
|
|
{
|
|
struct rcar_gen3_usb3 *r = phy_get_drvdata(p);
|
|
|
|
dev_vdbg(&r->phy->dev, "%s: enter (%d, %d, %d)\n", __func__,
|
|
r->usb3s_clk, r->usb_extal, r->ssc_range);
|
|
|
|
if (!r->usb3s_clk && r->usb_extal)
|
|
rcar_gen3_phy_usb3_select_usb_extal(r);
|
|
|
|
/* Enables VBUS detection anyway */
|
|
writew(VBUS_EN_VBUS_EN, r->base + USB30_VBUS_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct phy_ops rcar_gen3_phy_usb3_ops = {
|
|
.init = rcar_gen3_phy_usb3_init,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static const struct of_device_id rcar_gen3_phy_usb3_match_table[] = {
|
|
{ .compatible = "renesas,rcar-gen3-usb3-phy" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb3_match_table);
|
|
|
|
static int rcar_gen3_phy_usb3_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct rcar_gen3_usb3 *r;
|
|
struct phy_provider *provider;
|
|
int ret = 0;
|
|
struct clk *clk;
|
|
|
|
if (!dev->of_node) {
|
|
dev_err(dev, "This driver needs device tree\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
|
|
if (!r)
|
|
return -ENOMEM;
|
|
|
|
r->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(r->base))
|
|
return PTR_ERR(r->base);
|
|
|
|
clk = devm_clk_get(dev, "usb3s_clk");
|
|
if (!IS_ERR(clk) && !clk_prepare_enable(clk)) {
|
|
r->usb3s_clk = !!clk_get_rate(clk);
|
|
clk_disable_unprepare(clk);
|
|
}
|
|
clk = devm_clk_get(dev, "usb_extal");
|
|
if (!IS_ERR(clk) && !clk_prepare_enable(clk)) {
|
|
r->usb_extal = !!clk_get_rate(clk);
|
|
clk_disable_unprepare(clk);
|
|
}
|
|
|
|
if (!r->usb3s_clk && !r->usb_extal) {
|
|
dev_err(dev, "This driver needs usb3s_clk and/or usb_extal\n");
|
|
ret = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* devm_phy_create() will call pm_runtime_enable(&phy->dev);
|
|
* And then, phy-core will manage runtime pm for this device.
|
|
*/
|
|
pm_runtime_enable(dev);
|
|
|
|
r->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb3_ops);
|
|
if (IS_ERR(r->phy)) {
|
|
dev_err(dev, "Failed to create USB3 PHY\n");
|
|
ret = PTR_ERR(r->phy);
|
|
goto error;
|
|
}
|
|
|
|
of_property_read_u32(dev->of_node, "renesas,ssc-range", &r->ssc_range);
|
|
|
|
platform_set_drvdata(pdev, r);
|
|
phy_set_drvdata(r->phy, r);
|
|
|
|
provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
|
if (IS_ERR(provider)) {
|
|
dev_err(dev, "Failed to register PHY provider\n");
|
|
ret = PTR_ERR(provider);
|
|
goto error;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
pm_runtime_disable(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void rcar_gen3_phy_usb3_remove(struct platform_device *pdev)
|
|
{
|
|
pm_runtime_disable(&pdev->dev);
|
|
};
|
|
|
|
static struct platform_driver rcar_gen3_phy_usb3_driver = {
|
|
.driver = {
|
|
.name = "phy_rcar_gen3_usb3",
|
|
.of_match_table = rcar_gen3_phy_usb3_match_table,
|
|
},
|
|
.probe = rcar_gen3_phy_usb3_probe,
|
|
.remove_new = rcar_gen3_phy_usb3_remove,
|
|
};
|
|
module_platform_driver(rcar_gen3_phy_usb3_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 3.0 PHY");
|
|
MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>");
|