1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * StarFive JH7110 USB 2.0 PHY driver |
4 | * |
5 | * Copyright (C) 2023 StarFive Technology Co., Ltd. |
6 | * Author: Minda Chen <minda.chen@starfivetech.com> |
7 | */ |
8 | |
9 | #include <linux/bits.h> |
10 | #include <linux/clk.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <linux/module.h> |
14 | #include <linux/phy/phy.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/usb/of.h> |
17 | |
18 | #define USB_125M_CLK_RATE 125000000 |
19 | #define USB_LS_KEEPALIVE_OFF 0x4 |
20 | #define USB_LS_KEEPALIVE_ENABLE BIT(4) |
21 | |
22 | struct jh7110_usb2_phy { |
23 | struct phy *phy; |
24 | void __iomem *regs; |
25 | struct clk *usb_125m_clk; |
26 | struct clk *app_125m; |
27 | enum phy_mode mode; |
28 | }; |
29 | |
30 | static void usb2_set_ls_keepalive(struct jh7110_usb2_phy *phy, bool set) |
31 | { |
32 | unsigned int val; |
33 | |
34 | /* Host mode enable the LS speed keep-alive signal */ |
35 | val = readl(addr: phy->regs + USB_LS_KEEPALIVE_OFF); |
36 | if (set) |
37 | val |= USB_LS_KEEPALIVE_ENABLE; |
38 | else |
39 | val &= ~USB_LS_KEEPALIVE_ENABLE; |
40 | |
41 | writel(val, addr: phy->regs + USB_LS_KEEPALIVE_OFF); |
42 | } |
43 | |
44 | static int usb2_phy_set_mode(struct phy *_phy, |
45 | enum phy_mode mode, int submode) |
46 | { |
47 | struct jh7110_usb2_phy *phy = phy_get_drvdata(phy: _phy); |
48 | |
49 | switch (mode) { |
50 | case PHY_MODE_USB_HOST: |
51 | case PHY_MODE_USB_DEVICE: |
52 | case PHY_MODE_USB_OTG: |
53 | break; |
54 | default: |
55 | return -EINVAL; |
56 | } |
57 | |
58 | if (mode != phy->mode) { |
59 | dev_dbg(&_phy->dev, "Changing phy to %d\n" , mode); |
60 | phy->mode = mode; |
61 | usb2_set_ls_keepalive(phy, set: (mode != PHY_MODE_USB_DEVICE)); |
62 | } |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static int jh7110_usb2_phy_init(struct phy *_phy) |
68 | { |
69 | struct jh7110_usb2_phy *phy = phy_get_drvdata(phy: _phy); |
70 | int ret; |
71 | |
72 | ret = clk_set_rate(clk: phy->usb_125m_clk, USB_125M_CLK_RATE); |
73 | if (ret) |
74 | return ret; |
75 | |
76 | ret = clk_prepare_enable(clk: phy->app_125m); |
77 | if (ret) |
78 | return ret; |
79 | |
80 | return 0; |
81 | } |
82 | |
83 | static int jh7110_usb2_phy_exit(struct phy *_phy) |
84 | { |
85 | struct jh7110_usb2_phy *phy = phy_get_drvdata(phy: _phy); |
86 | |
87 | clk_disable_unprepare(clk: phy->app_125m); |
88 | |
89 | return 0; |
90 | } |
91 | |
92 | static const struct phy_ops jh7110_usb2_phy_ops = { |
93 | .init = jh7110_usb2_phy_init, |
94 | .exit = jh7110_usb2_phy_exit, |
95 | .set_mode = usb2_phy_set_mode, |
96 | .owner = THIS_MODULE, |
97 | }; |
98 | |
99 | static int jh7110_usb_phy_probe(struct platform_device *pdev) |
100 | { |
101 | struct jh7110_usb2_phy *phy; |
102 | struct device *dev = &pdev->dev; |
103 | struct phy_provider *phy_provider; |
104 | |
105 | phy = devm_kzalloc(dev, size: sizeof(*phy), GFP_KERNEL); |
106 | if (!phy) |
107 | return -ENOMEM; |
108 | |
109 | phy->usb_125m_clk = devm_clk_get(dev, id: "125m" ); |
110 | if (IS_ERR(ptr: phy->usb_125m_clk)) |
111 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->usb_125m_clk), |
112 | fmt: "Failed to get 125m clock\n" ); |
113 | |
114 | phy->app_125m = devm_clk_get(dev, id: "app_125m" ); |
115 | if (IS_ERR(ptr: phy->app_125m)) |
116 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->app_125m), |
117 | fmt: "Failed to get app 125m clock\n" ); |
118 | |
119 | phy->regs = devm_platform_ioremap_resource(pdev, index: 0); |
120 | if (IS_ERR(ptr: phy->regs)) |
121 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->regs), |
122 | fmt: "Failed to map phy base\n" ); |
123 | |
124 | phy->phy = devm_phy_create(dev, NULL, ops: &jh7110_usb2_phy_ops); |
125 | if (IS_ERR(ptr: phy->phy)) |
126 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->phy), |
127 | fmt: "Failed to create phy\n" ); |
128 | |
129 | phy_set_drvdata(phy: phy->phy, data: phy); |
130 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
131 | |
132 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
133 | } |
134 | |
135 | static const struct of_device_id jh7110_usb_phy_of_match[] = { |
136 | { .compatible = "starfive,jh7110-usb-phy" }, |
137 | { /* sentinel */ }, |
138 | }; |
139 | MODULE_DEVICE_TABLE(of, jh7110_usb_phy_of_match); |
140 | |
141 | static struct platform_driver jh7110_usb_phy_driver = { |
142 | .probe = jh7110_usb_phy_probe, |
143 | .driver = { |
144 | .of_match_table = jh7110_usb_phy_of_match, |
145 | .name = "jh7110-usb-phy" , |
146 | } |
147 | }; |
148 | module_platform_driver(jh7110_usb_phy_driver); |
149 | |
150 | MODULE_DESCRIPTION("StarFive JH7110 USB 2.0 PHY driver" ); |
151 | MODULE_AUTHOR("Minda Chen <minda.chen@starfivetech.com>" ); |
152 | MODULE_LICENSE("GPL" ); |
153 | |