xref: /openbmc/u-boot/drivers/phy/omap-usb2-phy.c (revision 0b366cf2)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * OMAP USB2 PHY LAYER
4  *
5  * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com
6  * Written by Jean-Jacques Hiblot <jjhiblot@ti.com>
7  */
8 
9 #include <common.h>
10 #include <asm/io.h>
11 #include <dm.h>
12 #include <errno.h>
13 #include <generic-phy.h>
14 #include <regmap.h>
15 #include <syscon.h>
16 
17 #define OMAP_USB2_CALIBRATE_FALSE_DISCONNECT	BIT(0)
18 
19 #define OMAP_DEV_PHY_PD		BIT(0)
20 #define OMAP_USB2_PHY_PD	BIT(28)
21 
22 #define USB2PHY_DISCON_BYP_LATCH	BIT(31)
23 #define USB2PHY_ANA_CONFIG1		(0x4c)
24 
25 DECLARE_GLOBAL_DATA_PTR;
26 
27 struct omap_usb2_phy {
28 	struct regmap *pwr_regmap;
29 	ulong flags;
30 	void *phy_base;
31 	u32 pwr_reg_offset;
32 };
33 
34 struct usb_phy_data {
35 	const char *label;
36 	u8 flags;
37 	u32 mask;
38 	u32 power_on;
39 	u32 power_off;
40 };
41 
42 static const struct usb_phy_data omap5_usb2_data = {
43 	.label = "omap5_usb2",
44 	.flags = 0,
45 	.mask = OMAP_DEV_PHY_PD,
46 	.power_off = OMAP_DEV_PHY_PD,
47 };
48 
49 static const struct usb_phy_data dra7x_usb2_data = {
50 	.label = "dra7x_usb2",
51 	.flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT,
52 	.mask = OMAP_DEV_PHY_PD,
53 	.power_off = OMAP_DEV_PHY_PD,
54 };
55 
56 static const struct usb_phy_data dra7x_usb2_phy2_data = {
57 	.label = "dra7x_usb2_phy2",
58 	.flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT,
59 	.mask = OMAP_USB2_PHY_PD,
60 	.power_off = OMAP_USB2_PHY_PD,
61 };
62 
63 static const struct udevice_id omap_usb2_id_table[] = {
64 	{
65 		.compatible = "ti,omap5-usb2",
66 		.data = (ulong)&omap5_usb2_data,
67 	},
68 	{
69 		.compatible = "ti,dra7x-usb2",
70 		.data = (ulong)&dra7x_usb2_data,
71 	},
72 	{
73 		.compatible = "ti,dra7x-usb2-phy2",
74 		.data = (ulong)&dra7x_usb2_phy2_data,
75 	},
76 	{},
77 };
78 
79 static int omap_usb_phy_power(struct phy *usb_phy, bool on)
80 {
81 	struct udevice *dev = usb_phy->dev;
82 	const struct usb_phy_data *data;
83 	const struct omap_usb2_phy *phy = dev_get_priv(dev);
84 	u32 val;
85 	int rc;
86 
87 	data = (const struct usb_phy_data *)dev_get_driver_data(dev);
88 	if (!data)
89 		return -EINVAL;
90 
91 	rc = regmap_read(phy->pwr_regmap, phy->pwr_reg_offset, &val);
92 	if (rc)
93 		return rc;
94 	val &= ~data->mask;
95 	if (on)
96 		val |= data->power_on;
97 	else
98 		val |= data->power_off;
99 	rc = regmap_write(phy->pwr_regmap, phy->pwr_reg_offset, val);
100 	if (rc)
101 		return rc;
102 
103 	return 0;
104 }
105 
106 static int omap_usb2_phy_init(struct phy *usb_phy)
107 {
108 	struct udevice *dev = usb_phy->dev;
109 	struct omap_usb2_phy *priv = dev_get_priv(dev);
110 	u32 val;
111 
112 	if (priv->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) {
113 		/*
114 		 *
115 		 * Reduce the sensitivity of internal PHY by enabling the
116 		 * DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register. This
117 		 * resolves issues with certain devices which can otherwise
118 		 * be prone to false disconnects.
119 		 *
120 		 */
121 		val = readl(priv->phy_base + USB2PHY_ANA_CONFIG1);
122 		val |= USB2PHY_DISCON_BYP_LATCH;
123 		writel(val, priv->phy_base + USB2PHY_ANA_CONFIG1);
124 	}
125 
126 	return 0;
127 }
128 
129 static int omap_usb2_phy_power_on(struct phy *usb_phy)
130 {
131 	return omap_usb_phy_power(usb_phy, true);
132 }
133 
134 static int omap_usb2_phy_power_off(struct phy *usb_phy)
135 {
136 	return omap_usb_phy_power(usb_phy, false);
137 }
138 
139 static int omap_usb2_phy_exit(struct phy *usb_phy)
140 {
141 	return omap_usb_phy_power(usb_phy, false);
142 }
143 
144 struct phy_ops omap_usb2_phy_ops = {
145 	.init = omap_usb2_phy_init,
146 	.power_on = omap_usb2_phy_power_on,
147 	.power_off = omap_usb2_phy_power_off,
148 	.exit = omap_usb2_phy_exit,
149 };
150 
151 int omap_usb2_phy_probe(struct udevice *dev)
152 {
153 	int rc;
154 	struct regmap *regmap;
155 	struct omap_usb2_phy *priv = dev_get_priv(dev);
156 	const struct usb_phy_data *data;
157 	u32 tmp[2];
158 
159 	data = (const struct usb_phy_data *)dev_get_driver_data(dev);
160 	if (!data)
161 		return -EINVAL;
162 
163 	if (data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) {
164 		u32 base = dev_read_addr(dev);
165 
166 		if (base == FDT_ADDR_T_NONE)
167 			return -EINVAL;
168 		priv->phy_base = (void *)base;
169 		priv->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT;
170 	}
171 
172 	regmap = syscon_regmap_lookup_by_phandle(dev, "syscon-phy-power");
173 	if (IS_ERR(regmap)) {
174 		printf("can't get regmap (err %ld)\n", PTR_ERR(regmap));
175 		return PTR_ERR(regmap);
176 	}
177 	priv->pwr_regmap = regmap;
178 
179 	rc =  dev_read_u32_array(dev, "syscon-phy-power", tmp, 2);
180 	if (rc) {
181 		printf("couldn't get power reg. offset (err %d)\n", rc);
182 		return rc;
183 	}
184 	priv->pwr_reg_offset = tmp[1];
185 
186 	return 0;
187 }
188 
189 U_BOOT_DRIVER(omap_usb2_phy) = {
190 	.name = "omap_usb2_phy",
191 	.id = UCLASS_PHY,
192 	.of_match = omap_usb2_id_table,
193 	.probe = omap_usb2_phy_probe,
194 	.ops = &omap_usb2_phy_ops,
195 	.priv_auto_alloc_size = sizeof(struct omap_usb2_phy),
196 };
197