summaryrefslogtreecommitdiff
path: root/drivers/phy/starfive/phy-jh7110-pcie.c
blob: a30582821d933d147f12c3e4bfe90a2dca43838e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// SPDX-License-Identifier: GPL-2.0+
/*
 * StarFive JH7110 PCIe 2.0 PHY driver
 *
 * Copyright (C) 2024 StarFive Technology Co., Ltd.
 * Author: Minda Chen <minda.chen@starfivetech.com>
 */
#include <asm/io.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <errno.h>
#include <generic-phy.h>
#include <regmap.h>
#include <soc.h>
#include <syscon.h>
#include <linux/bitops.h>
#include <linux/err.h>

#include "phy-jh7110-usb-syscon.h"

#define PCIE_KVCO_LEVEL_OFF			0x28
#define PCIE_USB3_PHY_PLL_CTL_OFF		0x7c
#define PCIE_USB3_PHY_SS_MODE			BIT(4)
#define PCIE_KVCO_TUNE_SIGNAL_OFF		0x80
#define PHY_KVCO_FINE_TUNE_LEVEL		0x91
#define PHY_KVCO_FINE_TUNE_SIGNALS		0xc

#define PCIE_USB3_PHY_MODE			0x1
#define PCIE_BUS_WIDTH				0x2
#define PCIE_USB3_PHY_ENABLE			0x1
#define PCIE_USB3_PHY_SPLIT			0x1

struct jh7110_pcie_phy {
	struct phy *phy;
	struct regmap *stg_syscon;
	struct regmap *sys_syscon;
	void __iomem *regs;
	struct regmap_field *phy_mode;
	struct regmap_field *bus_width;
	struct regmap_field *usb3_phy_en;
	struct regmap_field *usb_split;
	enum phy_mode mode;
};

static int phy_pcie_mode_set(struct jh7110_pcie_phy *data, bool usb_mode)
{
	unsigned int phy_mode, width, usb3_phy, ss_mode, split;

	/* default is PCIe mode */
	if (!data->stg_syscon || !data->sys_syscon) {
		if (usb_mode) {
			dev_err(data->phy->dev, "doesn't support USB3 mode\n");
			return -EINVAL;
		}
		return 0;
	}

	if (usb_mode) {
		phy_mode = PCIE_USB3_PHY_MODE;
		width = 0;
		usb3_phy = PCIE_USB3_PHY_ENABLE;
		ss_mode = PCIE_USB3_PHY_SS_MODE;
		split = 0;
	} else {
		phy_mode = 0;
		width = PCIE_BUS_WIDTH;
		usb3_phy = 0;
		ss_mode = 0;
		split = PCIE_USB3_PHY_SPLIT;
	}

	regmap_field_write(data->phy_mode, phy_mode);
	regmap_field_write(data->bus_width, width);
	regmap_field_write(data->usb3_phy_en, usb3_phy);
	clrsetbits_le32(data->regs + PCIE_USB3_PHY_PLL_CTL_OFF,
			PCIE_USB3_PHY_SS_MODE, ss_mode);
	regmap_field_write(data->usb_split, split);

	return 0;
}

static void phy_kvco_gain_set(struct jh7110_pcie_phy *phy)
{
	/* PCIe Multi-PHY PLL KVCO Gain fine tune settings: */
	writel(PHY_KVCO_FINE_TUNE_LEVEL, phy->regs + PCIE_KVCO_LEVEL_OFF);
	writel(PHY_KVCO_FINE_TUNE_SIGNALS, phy->regs + PCIE_KVCO_TUNE_SIGNAL_OFF);
}

static int jh7110_pcie_phy_set_mode(struct phy *phy,
				    enum phy_mode mode, int submode)
{
	struct udevice *dev = phy->dev;
	struct jh7110_pcie_phy *pcie_phy = dev_get_priv(dev);
	int ret;

	if (mode == pcie_phy->mode)
		return 0;

	switch (mode) {
	case PHY_MODE_USB_HOST:
	case PHY_MODE_USB_DEVICE:
	case PHY_MODE_USB_OTG:
		ret = phy_pcie_mode_set(pcie_phy, 1);
		if (ret)
			return ret;
		break;
	case PHY_MODE_PCIE:
		phy_pcie_mode_set(pcie_phy, 0);
		break;
	default:
		return -EINVAL;
	}

	dev_dbg(phy->dev, "Changing PHY mode to %d\n", mode);
	pcie_phy->mode = mode;

	return 0;
}

static const struct phy_ops jh7110_pcie_phy_ops = {
	.set_mode	= jh7110_pcie_phy_set_mode,
};

static int phy_stg_regfield_init(struct udevice *dev, int mode, int usb3)
{
	struct jh7110_pcie_phy *phy = dev_get_priv(dev);
	struct reg_field phy_mode = REG_FIELD(mode, 20, 21);
	struct reg_field bus_width = REG_FIELD(usb3, 2, 3);
	struct reg_field usb3_phy_en = REG_FIELD(usb3, 4, 4);

	phy->phy_mode = devm_regmap_field_alloc(dev, phy->stg_syscon, phy_mode);
	if (IS_ERR(phy->phy_mode)) {
		dev_err(dev, "PHY mode reg field init failed\n");
		return PTR_ERR(phy->phy_mode);
	}

	phy->bus_width = devm_regmap_field_alloc(dev, phy->stg_syscon, bus_width);
	if (IS_ERR(phy->bus_width)) {
		dev_err(dev, "PHY bus width reg field init failed\n");
		return PTR_ERR(phy->bus_width);
	}

	phy->usb3_phy_en = devm_regmap_field_alloc(dev, phy->stg_syscon, usb3_phy_en);
	if (IS_ERR(phy->usb3_phy_en)) {
		dev_err(dev, "USB3 PHY enable field init failed\n");
		return PTR_ERR(phy->bus_width);
	}

	return 0;
}

static int phy_sys_regfield_init(struct udevice *dev, int split)
{
	struct jh7110_pcie_phy *phy = dev_get_priv(dev);
	struct reg_field usb_split  = REG_FIELD(split, USB_PDRSTN_SPLIT_BIT, USB_PDRSTN_SPLIT_BIT);

	phy->usb_split = devm_regmap_field_alloc(dev, phy->sys_syscon, usb_split);
	if (IS_ERR(phy->usb_split)) {
		dev_err(dev, "USB split field init failed\n");
		return PTR_ERR(phy->usb_split);
	}

	return 0;
}

static int starfive_pcie_phy_get_syscon(struct udevice *dev)
{
	struct jh7110_pcie_phy *phy = dev_get_priv(dev);
	struct ofnode_phandle_args sys_phandle, stg_phandle;
	int ret;

	/* get corresponding syscon phandle */
	ret = dev_read_phandle_with_args(dev, "starfive,sys-syscon", NULL, 1, 0,
					 &sys_phandle);

	if (ret < 0) {
		dev_err(dev, "Can't get sys cfg phandle: %d\n", ret);
		return ret;
	}

	ret = dev_read_phandle_with_args(dev, "starfive,stg-syscon", NULL, 2, 0,
					 &stg_phandle);

	if (ret < 0) {
		dev_err(dev, "Can't get stg cfg phandle: %d\n", ret);
		return ret;
	}

	phy->sys_syscon = syscon_node_to_regmap(sys_phandle.node);
	/* get syscon register offset */
	if (!IS_ERR(phy->sys_syscon)) {
		ret = phy_sys_regfield_init(dev, SYSCON_USB_PDRSTN_REG_OFFSET);
		if (ret)
			return ret;
	} else {
		phy->sys_syscon = NULL;
	}

	phy->stg_syscon = syscon_node_to_regmap(stg_phandle.node);
	if (!IS_ERR(phy->stg_syscon))
		return phy_stg_regfield_init(dev, stg_phandle.args[0],
					     stg_phandle.args[1]);
	else
		phy->stg_syscon = NULL;

	return 0;
}

int jh7110_pcie_phy_probe(struct udevice *dev)
{
	struct jh7110_pcie_phy *phy = dev_get_priv(dev);
	int rc;

	phy->regs = dev_read_addr_ptr(dev);
	if (!phy->regs)
		return -EINVAL;

	rc = starfive_pcie_phy_get_syscon(dev);
	if (rc)
		return rc;

	phy_kvco_gain_set(phy);

	return 0;
}

static const struct udevice_id jh7110_pcie_phy[] = {
	{ .compatible = "starfive,jh7110-pcie-phy"},
	{},
};

U_BOOT_DRIVER(jh7110_pcie_phy) = {
	.name = "jh7110_pcie_phy",
	.id = UCLASS_PHY,
	.of_match = jh7110_pcie_phy,
	.probe = jh7110_pcie_phy_probe,
	.ops = &jh7110_pcie_phy_ops,
	.priv_auto	= sizeof(struct jh7110_pcie_phy),
};