| 1 | |
| 2 |
|
| 3 | |
| 4 | * Sunplus SP7021 USB 2.0 phy driver
|
| 5 | *
|
| 6 | * Copyright (C) 2022 Sunplus Technology Inc., All rights reserved.
|
| 7 | *
|
| 8 | * Note 1 : non-posted write command for the registers accesses of
|
| 9 | * Sunplus SP7021.
|
| 10 | *
|
| 11 |
|
| 12 |
|
| 13 | #include <linux/bitfield.h>
|
| 14 | #include <linux/clk.h>
|
| 15 | #include <linux/delay.h>
|
| 16 | #include <linux/io.h>
|
| 17 | #include <linux/module.h>
|
| 18 | #include <linux/nvmem-consumer.h>
|
| 19 | #include <linux/of.h>
|
| 20 | #include <linux/phy/phy.h>
|
| 21 | #include <linux/platform_device.h>
|
| 22 | #include <linux/reset.h>
|
| 23 |
|
| 24 | #define HIGH_MASK_BITS GENMASK(31, 16)
|
| 25 | #define LOW_MASK_BITS GENMASK(15, 0)
|
| 26 | #define OTP_DISC_LEVEL_DEFAULT 0xd
|
| 27 |
|
| 28 |
|
| 29 | #define CONFIG1 0x4
|
| 30 | #define J_HS_TX_PWRSAV BIT(5)
|
| 31 | #define CONFIG3 0xc
|
| 32 | #define J_FORCE_DISC_ON BIT(5)
|
| 33 | #define J_DEBUG_CTRL_ADDR_MACRO BIT(0)
|
| 34 | #define CONFIG7 0x1c
|
| 35 | #define J_DISC 0X1f
|
| 36 | #define CONFIG9 0x24
|
| 37 | #define J_ECO_PATH BIT(6)
|
| 38 | #define CONFIG16 0x40
|
| 39 | #define J_TBCWAIT_MASK GENMASK(6, 5)
|
| 40 | #define J_TBCWAIT_1P1_MS FIELD_PREP(J_TBCWAIT_MASK, 0)
|
| 41 | #define J_TVDM_SRC_DIS_MASK GENMASK(4, 3)
|
| 42 | #define J_TVDM_SRC_DIS_8P2_MS FIELD_PREP(J_TVDM_SRC_DIS_MASK, 3)
|
| 43 | #define J_TVDM_SRC_EN_MASK GENMASK(2, 1)
|
| 44 | #define J_TVDM_SRC_EN_1P6_MS FIELD_PREP(J_TVDM_SRC_EN_MASK, 0)
|
| 45 | #define J_BC_EN BIT(0)
|
| 46 | #define CONFIG17 0x44
|
| 47 | #define IBG_TRIM0_MASK GENMASK(7, 5)
|
| 48 | #define IBG_TRIM0_SSLVHT FIELD_PREP(IBG_TRIM0_MASK, 4)
|
| 49 | #define J_VDATREE_TRIM_MASK GENMASK(4, 1)
|
| 50 | #define J_VDATREE_TRIM_DEFAULT FIELD_PREP(J_VDATREE_TRIM_MASK, 9)
|
| 51 | #define CONFIG23 0x5c
|
| 52 | #define PROB_MASK GENMASK(5, 3)
|
| 53 | #define PROB FIELD_PREP(PROB_MASK, 7)
|
| 54 |
|
| 55 |
|
| 56 | #define UPHY_CONTROL0 0x0
|
| 57 | #define UPHY_CONTROL1 0x4
|
| 58 | #define UPHY_CONTROL2 0x8
|
| 59 | #define MO1_UPHY_RX_CLK_SEL BIT(6)
|
| 60 | #define MASK_MO1_UPHY_RX_CLK_SEL BIT(6 + 16)
|
| 61 | #define UPHY_CONTROL3 0xc
|
| 62 | #define MO1_UPHY_PLL_POWER_OFF_SEL BIT(7)
|
| 63 | #define MASK_MO1_UPHY_PLL_POWER_OFF_SEL BIT(7 + 16)
|
| 64 | #define MO1_UPHY_PLL_POWER_OFF BIT(3)
|
| 65 | #define MASK_UPHY_PLL_POWER_OFF BIT(3 + 16)
|
| 66 |
|
| 67 | struct sp_usbphy {
|
| 68 | struct device *dev;
|
| 69 | struct resource *phy_res_mem;
|
| 70 | struct resource *moon4_res_mem;
|
| 71 | struct reset_control *rstc;
|
| 72 | struct clk *phy_clk;
|
| 73 | void __iomem *phy_regs;
|
| 74 | void __iomem *moon4_regs;
|
| 75 | u32 disc_vol_addr_off;
|
| 76 | };
|
| 77 |
|
| 78 | static int update_disc_vol(struct sp_usbphy *usbphy)
|
| 79 | {
|
| 80 | struct nvmem_cell *cell;
|
| 81 | char *disc_name = "disc_vol";
|
| 82 | ssize_t otp_l = 0;
|
| 83 | char *otp_v;
|
| 84 | u32 val, set;
|
| 85 |
|
| 86 | cell = nvmem_cell_get(usbphy->dev, disc_name);
|
| 87 | if (IS_ERR_OR_NULL(cell)) {
|
| 88 | if (PTR_ERR(cell) == -EPROBE_DEFER)
|
| 89 | return -EPROBE_DEFER;
|
| 90 | }
|
| 91 |
|
| 92 | otp_v = nvmem_cell_read(cell, &otp_l);
|
| 93 | nvmem_cell_put(cell);
|
| 94 |
|
| 95 | if (!IS_ERR(otp_v)) {
|
| 96 | set = *(otp_v + 1);
|
| 97 | set = (set << (sizeof(char) * 8)) | *otp_v;
|
| 98 | set = (set >> usbphy->disc_vol_addr_off) & J_DISC;
|
| 99 | }
|
| 100 |
|
| 101 | if (IS_ERR(otp_v) || set == 0)
|
| 102 | set = OTP_DISC_LEVEL_DEFAULT;
|
| 103 |
|
| 104 | val = readl(usbphy->phy_regs + CONFIG7);
|
| 105 | val = (val & ~J_DISC) | set;
|
| 106 | writel(val, usbphy->phy_regs + CONFIG7);
|
| 107 |
|
| 108 | return 0;
|
| 109 | }
|
| 110 |
|
| 111 | static int sp_uphy_init(struct phy *phy)
|
| 112 | {
|
| 113 | struct sp_usbphy *usbphy = phy_get_drvdata(phy);
|
| 114 | u32 val;
|
| 115 | int ret;
|
| 116 |
|
| 117 | ret = clk_prepare_enable(usbphy->phy_clk);
|
| 118 | if (ret)
|
| 119 | return ret;
|
| 120 |
|
| 121 | ret = reset_control_deassert(usbphy->rstc);
|
| 122 | if (ret)
|
| 123 | goto err_clk;
|
| 124 |
|
| 125 |
|
| 126 | writel(HIGH_MASK_BITS | 0x4002, usbphy->moon4_regs + UPHY_CONTROL0);
|
| 127 | writel(HIGH_MASK_BITS | 0x8747, usbphy->moon4_regs + UPHY_CONTROL1);
|
| 128 |
|
| 129 |
|
| 130 | ret = update_disc_vol(usbphy);
|
| 131 | if (ret < 0)
|
| 132 | goto err_reset;
|
| 133 |
|
| 134 |
|
| 135 | val = readl(usbphy->phy_regs + CONFIG9);
|
| 136 | val &= ~(J_ECO_PATH);
|
| 137 | writel(val, usbphy->phy_regs + CONFIG9);
|
| 138 |
|
| 139 | val = readl(usbphy->phy_regs + CONFIG1);
|
| 140 | val &= ~(J_HS_TX_PWRSAV);
|
| 141 | writel(val, usbphy->phy_regs + CONFIG1);
|
| 142 |
|
| 143 | val = readl(usbphy->phy_regs + CONFIG23);
|
| 144 | val = (val & ~PROB) | PROB;
|
| 145 | writel(val, usbphy->phy_regs + CONFIG23);
|
| 146 |
|
| 147 |
|
| 148 | writel(MASK_MO1_UPHY_RX_CLK_SEL | MO1_UPHY_RX_CLK_SEL,
|
| 149 | usbphy->moon4_regs + UPHY_CONTROL2);
|
| 150 |
|
| 151 |
|
| 152 | writel(J_TBCWAIT_1P1_MS | J_TVDM_SRC_DIS_8P2_MS | J_TVDM_SRC_EN_1P6_MS | J_BC_EN,
|
| 153 | usbphy->phy_regs + CONFIG16);
|
| 154 | writel(IBG_TRIM0_SSLVHT | J_VDATREE_TRIM_DEFAULT, usbphy->phy_regs + CONFIG17);
|
| 155 |
|
| 156 |
|
| 157 | writel(J_FORCE_DISC_ON | J_DEBUG_CTRL_ADDR_MACRO, usbphy->phy_regs + CONFIG3);
|
| 158 |
|
| 159 | return 0;
|
| 160 |
|
| 161 | err_reset:
|
| 162 | reset_control_assert(usbphy->rstc);
|
| 163 | err_clk:
|
| 164 | clk_disable_unprepare(usbphy->phy_clk);
|
| 165 |
|
| 166 | return ret;
|
| 167 | }
|
| 168 |
|
| 169 | static int sp_uphy_power_on(struct phy *phy)
|
| 170 | {
|
| 171 | struct sp_usbphy *usbphy = phy_get_drvdata(phy);
|
| 172 | u32 pll_pwr_on, pll_pwr_off;
|
| 173 |
|
| 174 |
|
| 175 | pll_pwr_off = (readl(usbphy->moon4_regs + UPHY_CONTROL3) & ~LOW_MASK_BITS)
|
| 176 | | MO1_UPHY_PLL_POWER_OFF_SEL | MO1_UPHY_PLL_POWER_OFF;
|
| 177 | pll_pwr_on = (readl(usbphy->moon4_regs + UPHY_CONTROL3) & ~LOW_MASK_BITS)
|
| 178 | | MO1_UPHY_PLL_POWER_OFF_SEL;
|
| 179 |
|
| 180 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off,
|
| 181 | usbphy->moon4_regs + UPHY_CONTROL3);
|
| 182 | mdelay(1);
|
| 183 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_on,
|
| 184 | usbphy->moon4_regs + UPHY_CONTROL3);
|
| 185 | mdelay(1);
|
| 186 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off,
|
| 187 | usbphy->moon4_regs + UPHY_CONTROL3);
|
| 188 | mdelay(1);
|
| 189 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_on,
|
| 190 | usbphy->moon4_regs + UPHY_CONTROL3);
|
| 191 | mdelay(1);
|
| 192 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | 0x0,
|
| 193 | usbphy->moon4_regs + UPHY_CONTROL3);
|
| 194 |
|
| 195 | return 0;
|
| 196 | }
|
| 197 |
|
| 198 | static int sp_uphy_power_off(struct phy *phy)
|
| 199 | {
|
| 200 | struct sp_usbphy *usbphy = phy_get_drvdata(phy);
|
| 201 | u32 pll_pwr_off;
|
| 202 |
|
| 203 | pll_pwr_off = (readl(usbphy->moon4_regs + UPHY_CONTROL3) & ~LOW_MASK_BITS)
|
| 204 | | MO1_UPHY_PLL_POWER_OFF_SEL | MO1_UPHY_PLL_POWER_OFF;
|
| 205 |
|
| 206 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off,
|
| 207 | usbphy->moon4_regs + UPHY_CONTROL3);
|
| 208 | mdelay(1);
|
| 209 | writel(MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | 0x0,
|
| 210 | usbphy->moon4_regs + UPHY_CONTROL3);
|
| 211 |
|
| 212 | return 0;
|
| 213 | }
|
| 214 |
|
| 215 | static int sp_uphy_exit(struct phy *phy)
|
| 216 | {
|
| 217 | struct sp_usbphy *usbphy = phy_get_drvdata(phy);
|
| 218 |
|
| 219 | reset_control_assert(usbphy->rstc);
|
| 220 | clk_disable_unprepare(usbphy->phy_clk);
|
| 221 |
|
| 222 | return 0;
|
| 223 | }
|
| 224 |
|
| 225 | static const struct phy_ops sp_uphy_ops = {
|
| 226 | .init = sp_uphy_init,
|
| 227 | .power_on = sp_uphy_power_on,
|
| 228 | .power_off = sp_uphy_power_off,
|
| 229 | .exit = sp_uphy_exit,
|
| 230 | };
|
| 231 |
|
| 232 | static const struct of_device_id sp_uphy_dt_ids[] = {
|
| 233 | {.compatible = "sunplus,sp7021-usb2-phy", },
|
| 234 | { }
|
| 235 | };
|
| 236 | MODULE_DEVICE_TABLE(of, sp_uphy_dt_ids);
|
| 237 |
|
| 238 | static int sp_usb_phy_probe(struct platform_device *pdev)
|
| 239 | {
|
| 240 | struct sp_usbphy *usbphy;
|
| 241 | struct phy_provider *phy_provider;
|
| 242 | struct phy *phy;
|
| 243 | int ret;
|
| 244 |
|
| 245 | usbphy = devm_kzalloc(&pdev->dev, sizeof(*usbphy), GFP_KERNEL);
|
| 246 | if (!usbphy)
|
| 247 | return -ENOMEM;
|
| 248 |
|
| 249 | usbphy->dev = &pdev->dev;
|
| 250 |
|
| 251 | usbphy->phy_res_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy");
|
| 252 | usbphy->phy_regs = devm_ioremap_resource(&pdev->dev, usbphy->phy_res_mem);
|
| 253 | if (IS_ERR(usbphy->phy_regs))
|
| 254 | return PTR_ERR(usbphy->phy_regs);
|
| 255 |
|
| 256 | usbphy->moon4_res_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "moon4");
|
| 257 | if (!usbphy->moon4_res_mem)
|
| 258 | return -EINVAL;
|
| 259 |
|
| 260 | usbphy->moon4_regs = devm_ioremap(&pdev->dev, usbphy->moon4_res_mem->start,
|
| 261 | resource_size(usbphy->moon4_res_mem));
|
| 262 | if (!usbphy->moon4_regs)
|
| 263 | return -ENOMEM;
|
| 264 |
|
| 265 | usbphy->phy_clk = devm_clk_get(&pdev->dev, NULL);
|
| 266 | if (IS_ERR(usbphy->phy_clk))
|
| 267 | return PTR_ERR(usbphy->phy_clk);
|
| 268 |
|
| 269 | usbphy->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
|
| 270 | if (IS_ERR(usbphy->rstc))
|
| 271 | return PTR_ERR(usbphy->rstc);
|
| 272 |
|
| 273 | of_property_read_u32(pdev->dev.of_node, "sunplus,disc-vol-addr-off",
|
| 274 | &usbphy->disc_vol_addr_off);
|
| 275 |
|
| 276 | phy = devm_phy_create(&pdev->dev, NULL, &sp_uphy_ops);
|
| 277 | if (IS_ERR(phy)) {
|
| 278 | ret = PTR_ERR(phy);
|
| 279 | return ret;
|
| 280 | }
|
| 281 |
|
| 282 | phy_set_drvdata(phy, usbphy);
|
| 283 | phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
|
| 284 |
|
| 285 | return PTR_ERR_OR_ZERO(phy_provider);
|
| 286 | }
|
| 287 |
|
| 288 | static struct platform_driver sunplus_usb_phy_driver = {
|
| 289 | .probe = sp_usb_phy_probe,
|
| 290 | .driver = {
|
| 291 | .name = "sunplus-usb2-phy",
|
| 292 | .of_match_table = sp_uphy_dt_ids,
|
| 293 | },
|
| 294 | };
|
| 295 | module_platform_driver(sunplus_usb_phy_driver);
|
| 296 |
|
| 297 | MODULE_AUTHOR("Vincent Shih <[email protected]>");
|
| 298 | MODULE_DESCRIPTION("Sunplus USB 2.0 phy driver");
|
| 299 | MODULE_LICENSE("GPL");
|