Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

phy: nuvoton: add new driver for the Nuvoton MA35 SoC USB 2.0 PHY

Nuvoton MA35 SoCs support DWC2 USB controller.
Add the driver to drive the USB 2.0 PHY transceivers.

Signed-off-by: Hui-Ping Chen <hpchen0nvt@gmail.com>
Link: https://lore.kernel.org/r/20240805030356.14565-3-hpchen0nvt@gmail.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>

authored by

Hui-Ping Chen and committed by
Vinod Koul
b48baf69 c174f1c6

+160
+1
drivers/phy/Kconfig
··· 95 95 source "drivers/phy/microchip/Kconfig" 96 96 source "drivers/phy/motorola/Kconfig" 97 97 source "drivers/phy/mscc/Kconfig" 98 + source "drivers/phy/nuvoton/Kconfig" 98 99 source "drivers/phy/qualcomm/Kconfig" 99 100 source "drivers/phy/ralink/Kconfig" 100 101 source "drivers/phy/realtek/Kconfig"
+1
drivers/phy/Makefile
··· 25 25 microchip/ \ 26 26 motorola/ \ 27 27 mscc/ \ 28 + nuvoton/ \ 28 29 qualcomm/ \ 29 30 ralink/ \ 30 31 realtek/ \
+12
drivers/phy/nuvoton/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + # 3 + # PHY drivers for Nuvoton MA35 platforms 4 + # 5 + config PHY_MA35_USB 6 + tristate "Nuvoton MA35 USB2.0 PHY driver" 7 + depends on ARCH_MA35 || COMPILE_TEST 8 + depends on OF 9 + select GENERIC_PHY 10 + help 11 + Enable this to support the USB2.0 PHY on the Nuvoton MA35 12 + series SoCs.
+3
drivers/phy/nuvoton/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + obj-$(CONFIG_PHY_MA35_USB) += phy-ma35d1-usb2.o
+143
drivers/phy/nuvoton/phy-ma35d1-usb2.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (C) 2024 Nuvoton Technology Corp. 4 + */ 5 + #include <linux/bitfield.h> 6 + #include <linux/clk.h> 7 + #include <linux/delay.h> 8 + #include <linux/io.h> 9 + #include <linux/kernel.h> 10 + #include <linux/mfd/syscon.h> 11 + #include <linux/module.h> 12 + #include <linux/of.h> 13 + #include <linux/phy/phy.h> 14 + #include <linux/platform_device.h> 15 + #include <linux/regmap.h> 16 + 17 + /* USB PHY Miscellaneous Control Register */ 18 + #define MA35_SYS_REG_USBPMISCR 0x60 19 + #define PHY0POR BIT(0) /* PHY Power-On Reset Control Bit */ 20 + #define PHY0SUSPEND BIT(1) /* PHY Suspend; 0: suspend, 1: operaion */ 21 + #define PHY0COMN BIT(2) /* PHY Common Block Power-Down Control */ 22 + #define PHY0DEVCKSTB BIT(10) /* PHY 60 MHz UTMI clock stable bit */ 23 + 24 + struct ma35_usb_phy { 25 + struct clk *clk; 26 + struct device *dev; 27 + struct regmap *sysreg; 28 + }; 29 + 30 + static int ma35_usb_phy_power_on(struct phy *phy) 31 + { 32 + struct ma35_usb_phy *p_phy = phy_get_drvdata(phy); 33 + unsigned int val; 34 + int ret; 35 + 36 + ret = clk_prepare_enable(p_phy->clk); 37 + if (ret < 0) { 38 + dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret); 39 + return ret; 40 + } 41 + 42 + regmap_read(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, &val); 43 + if (val & PHY0SUSPEND) { 44 + /* 45 + * USB PHY0 is in operation mode already 46 + * make sure USB PHY 60 MHz UTMI Interface Clock ready 47 + */ 48 + ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val, 49 + val & PHY0DEVCKSTB, 10, 1000); 50 + if (ret == 0) 51 + return 0; 52 + } 53 + 54 + /* 55 + * reset USB PHY0. 56 + * wait until USB PHY0 60 MHz UTMI Interface Clock ready 57 + */ 58 + regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND)); 59 + udelay(20); 60 + 61 + /* make USB PHY0 enter operation mode */ 62 + regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, PHY0SUSPEND); 63 + 64 + /* make sure USB PHY 60 MHz UTMI Interface Clock ready */ 65 + ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val, 66 + val & PHY0DEVCKSTB, 10, 1000); 67 + if (ret == -ETIMEDOUT) { 68 + dev_err(p_phy->dev, "Check PHY clock, Timeout: %d\n", ret); 69 + clk_disable_unprepare(p_phy->clk); 70 + return ret; 71 + } 72 + 73 + return 0; 74 + } 75 + 76 + static int ma35_usb_phy_power_off(struct phy *phy) 77 + { 78 + struct ma35_usb_phy *p_phy = phy_get_drvdata(phy); 79 + 80 + clk_disable_unprepare(p_phy->clk); 81 + return 0; 82 + } 83 + 84 + static const struct phy_ops ma35_usb_phy_ops = { 85 + .power_on = ma35_usb_phy_power_on, 86 + .power_off = ma35_usb_phy_power_off, 87 + .owner = THIS_MODULE, 88 + }; 89 + 90 + static int ma35_usb_phy_probe(struct platform_device *pdev) 91 + { 92 + struct phy_provider *provider; 93 + struct ma35_usb_phy *p_phy; 94 + struct phy *phy; 95 + 96 + p_phy = devm_kzalloc(&pdev->dev, sizeof(*p_phy), GFP_KERNEL); 97 + if (!p_phy) 98 + return -ENOMEM; 99 + 100 + p_phy->dev = &pdev->dev; 101 + platform_set_drvdata(pdev, p_phy); 102 + 103 + p_phy->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "nuvoton,sys"); 104 + if (IS_ERR(p_phy->sysreg)) 105 + return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->sysreg), 106 + "Failed to get SYS registers\n"); 107 + 108 + p_phy->clk = of_clk_get(pdev->dev.of_node, 0); 109 + if (IS_ERR(p_phy->clk)) 110 + return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->clk), 111 + "failed to find usb_phy clock\n"); 112 + 113 + phy = devm_phy_create(&pdev->dev, NULL, &ma35_usb_phy_ops); 114 + if (IS_ERR(phy)) 115 + return dev_err_probe(&pdev->dev, PTR_ERR(phy), "Failed to create PHY\n"); 116 + 117 + phy_set_drvdata(phy, p_phy); 118 + 119 + provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); 120 + if (IS_ERR(provider)) 121 + return dev_err_probe(&pdev->dev, PTR_ERR(provider), 122 + "Failed to register PHY provider\n"); 123 + return 0; 124 + } 125 + 126 + static const struct of_device_id ma35_usb_phy_of_match[] = { 127 + { .compatible = "nuvoton,ma35d1-usb2-phy", }, 128 + { }, 129 + }; 130 + MODULE_DEVICE_TABLE(of, ma35_usb_phy_of_match); 131 + 132 + static struct platform_driver ma35_usb_phy_driver = { 133 + .probe = ma35_usb_phy_probe, 134 + .driver = { 135 + .name = "ma35d1-usb2-phy", 136 + .of_match_table = ma35_usb_phy_of_match, 137 + }, 138 + }; 139 + module_platform_driver(ma35_usb_phy_driver); 140 + 141 + MODULE_DESCRIPTION("Nuvoton ma35d1 USB2.0 PHY driver"); 142 + MODULE_AUTHOR("Hui-Ping Chen <hpchen0nvt@gmail.com>"); 143 + MODULE_LICENSE("GPL");