1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * StarFive JH7110 PCIe 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/mfd/syscon.h> |
15 | #include <linux/phy/phy.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/regmap.h> |
18 | |
19 | #define PCIE_KVCO_LEVEL_OFF 0x28 |
20 | #define PCIE_USB3_PHY_PLL_CTL_OFF 0x7c |
21 | #define PCIE_KVCO_TUNE_SIGNAL_OFF 0x80 |
22 | #define PCIE_USB3_PHY_ENABLE BIT(4) |
23 | #define PHY_KVCO_FINE_TUNE_LEVEL 0x91 |
24 | #define PHY_KVCO_FINE_TUNE_SIGNALS 0xc |
25 | |
26 | #define USB_PDRSTN_SPLIT BIT(17) |
27 | |
28 | #define PCIE_PHY_MODE BIT(20) |
29 | #define PCIE_PHY_MODE_MASK GENMASK(21, 20) |
30 | #define PCIE_USB3_BUS_WIDTH_MASK GENMASK(3, 2) |
31 | #define PCIE_USB3_BUS_WIDTH BIT(3) |
32 | #define PCIE_USB3_RATE_MASK GENMASK(6, 5) |
33 | #define PCIE_USB3_RX_STANDBY_MASK BIT(7) |
34 | #define PCIE_USB3_PHY_ENABLE BIT(4) |
35 | |
36 | struct jh7110_pcie_phy { |
37 | struct phy *phy; |
38 | struct regmap *stg_syscon; |
39 | struct regmap *sys_syscon; |
40 | void __iomem *regs; |
41 | u32 sys_phy_connect; |
42 | u32 stg_pcie_mode; |
43 | u32 stg_pcie_usb; |
44 | enum phy_mode mode; |
45 | }; |
46 | |
47 | static int phy_usb3_mode_set(struct jh7110_pcie_phy *data) |
48 | { |
49 | if (!data->stg_syscon || !data->sys_syscon) { |
50 | dev_err(&data->phy->dev, "doesn't support usb3 mode\n" ); |
51 | return -EINVAL; |
52 | } |
53 | |
54 | regmap_update_bits(map: data->stg_syscon, reg: data->stg_pcie_mode, |
55 | PCIE_PHY_MODE_MASK, PCIE_PHY_MODE); |
56 | regmap_update_bits(map: data->stg_syscon, reg: data->stg_pcie_usb, |
57 | PCIE_USB3_BUS_WIDTH_MASK, val: 0); |
58 | regmap_update_bits(map: data->stg_syscon, reg: data->stg_pcie_usb, |
59 | PCIE_USB3_PHY_ENABLE, PCIE_USB3_PHY_ENABLE); |
60 | |
61 | /* Connect usb 3.0 phy mode */ |
62 | regmap_update_bits(map: data->sys_syscon, reg: data->sys_phy_connect, |
63 | USB_PDRSTN_SPLIT, val: 0); |
64 | |
65 | /* Configuare spread-spectrum mode: down-spread-spectrum */ |
66 | writel(PCIE_USB3_PHY_ENABLE, addr: data->regs + PCIE_USB3_PHY_PLL_CTL_OFF); |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | static void phy_pcie_mode_set(struct jh7110_pcie_phy *data) |
72 | { |
73 | u32 val; |
74 | |
75 | /* default is PCIe mode */ |
76 | if (!data->stg_syscon || !data->sys_syscon) |
77 | return; |
78 | |
79 | regmap_update_bits(map: data->stg_syscon, reg: data->stg_pcie_mode, |
80 | PCIE_PHY_MODE_MASK, val: 0); |
81 | regmap_update_bits(map: data->stg_syscon, reg: data->stg_pcie_usb, |
82 | PCIE_USB3_BUS_WIDTH_MASK, |
83 | PCIE_USB3_BUS_WIDTH); |
84 | regmap_update_bits(map: data->stg_syscon, reg: data->stg_pcie_usb, |
85 | PCIE_USB3_PHY_ENABLE, val: 0); |
86 | |
87 | regmap_update_bits(map: data->sys_syscon, reg: data->sys_phy_connect, |
88 | USB_PDRSTN_SPLIT, val: 0); |
89 | |
90 | val = readl(addr: data->regs + PCIE_USB3_PHY_PLL_CTL_OFF); |
91 | val &= ~PCIE_USB3_PHY_ENABLE; |
92 | writel(val, addr: data->regs + PCIE_USB3_PHY_PLL_CTL_OFF); |
93 | } |
94 | |
95 | static void phy_kvco_gain_set(struct jh7110_pcie_phy *phy) |
96 | { |
97 | /* PCIe Multi-PHY PLL KVCO Gain fine tune settings: */ |
98 | writel(PHY_KVCO_FINE_TUNE_LEVEL, addr: phy->regs + PCIE_KVCO_LEVEL_OFF); |
99 | writel(PHY_KVCO_FINE_TUNE_SIGNALS, addr: phy->regs + PCIE_KVCO_TUNE_SIGNAL_OFF); |
100 | } |
101 | |
102 | static int jh7110_pcie_phy_set_mode(struct phy *_phy, |
103 | enum phy_mode mode, int submode) |
104 | { |
105 | struct jh7110_pcie_phy *phy = phy_get_drvdata(phy: _phy); |
106 | int ret; |
107 | |
108 | if (mode == phy->mode) |
109 | return 0; |
110 | |
111 | switch (mode) { |
112 | case PHY_MODE_USB_HOST: |
113 | case PHY_MODE_USB_DEVICE: |
114 | case PHY_MODE_USB_OTG: |
115 | ret = phy_usb3_mode_set(data: phy); |
116 | if (ret) |
117 | return ret; |
118 | break; |
119 | case PHY_MODE_PCIE: |
120 | phy_pcie_mode_set(data: phy); |
121 | break; |
122 | default: |
123 | return -EINVAL; |
124 | } |
125 | |
126 | dev_dbg(&_phy->dev, "Changing phy mode to %d\n" , mode); |
127 | phy->mode = mode; |
128 | |
129 | return 0; |
130 | } |
131 | |
132 | static const struct phy_ops jh7110_pcie_phy_ops = { |
133 | .set_mode = jh7110_pcie_phy_set_mode, |
134 | .owner = THIS_MODULE, |
135 | }; |
136 | |
137 | static int jh7110_pcie_phy_probe(struct platform_device *pdev) |
138 | { |
139 | struct jh7110_pcie_phy *phy; |
140 | struct device *dev = &pdev->dev; |
141 | struct phy_provider *phy_provider; |
142 | u32 args[2]; |
143 | |
144 | phy = devm_kzalloc(dev, size: sizeof(*phy), GFP_KERNEL); |
145 | if (!phy) |
146 | return -ENOMEM; |
147 | |
148 | phy->regs = devm_platform_ioremap_resource(pdev, index: 0); |
149 | if (IS_ERR(ptr: phy->regs)) |
150 | return PTR_ERR(ptr: phy->regs); |
151 | |
152 | phy->phy = devm_phy_create(dev, NULL, ops: &jh7110_pcie_phy_ops); |
153 | if (IS_ERR(ptr: phy->phy)) |
154 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->phy), |
155 | fmt: "Failed to map phy base\n" ); |
156 | |
157 | phy->sys_syscon = |
158 | syscon_regmap_lookup_by_phandle_args(np: pdev->dev.of_node, |
159 | property: "starfive,sys-syscon" , |
160 | arg_count: 1, out_args: args); |
161 | |
162 | if (!IS_ERR_OR_NULL(ptr: phy->sys_syscon)) |
163 | phy->sys_phy_connect = args[0]; |
164 | else |
165 | phy->sys_syscon = NULL; |
166 | |
167 | phy->stg_syscon = |
168 | syscon_regmap_lookup_by_phandle_args(np: pdev->dev.of_node, |
169 | property: "starfive,stg-syscon" , |
170 | arg_count: 2, out_args: args); |
171 | |
172 | if (!IS_ERR_OR_NULL(ptr: phy->stg_syscon)) { |
173 | phy->stg_pcie_mode = args[0]; |
174 | phy->stg_pcie_usb = args[1]; |
175 | } else { |
176 | phy->stg_syscon = NULL; |
177 | } |
178 | |
179 | phy_kvco_gain_set(phy); |
180 | |
181 | phy_set_drvdata(phy: phy->phy, data: phy); |
182 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
183 | |
184 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
185 | } |
186 | |
187 | static const struct of_device_id jh7110_pcie_phy_of_match[] = { |
188 | { .compatible = "starfive,jh7110-pcie-phy" }, |
189 | { /* sentinel */ }, |
190 | }; |
191 | MODULE_DEVICE_TABLE(of, jh7110_pcie_phy_of_match); |
192 | |
193 | static struct platform_driver jh7110_pcie_phy_driver = { |
194 | .probe = jh7110_pcie_phy_probe, |
195 | .driver = { |
196 | .of_match_table = jh7110_pcie_phy_of_match, |
197 | .name = "jh7110-pcie-phy" , |
198 | } |
199 | }; |
200 | module_platform_driver(jh7110_pcie_phy_driver); |
201 | |
202 | MODULE_DESCRIPTION("StarFive JH7110 PCIe 2.0 PHY driver" ); |
203 | MODULE_AUTHOR("Minda Chen <minda.chen@starfivetech.com>" ); |
204 | MODULE_LICENSE("GPL" ); |
205 | |