1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Samsung SoC USB 1.1/2.0 PHY driver - S5PV210 support
4 *
5 * Copyright (C) 2013 Samsung Electronics Co., Ltd.
6 * Authors: Kamil Debski <k.debski@samsung.com>
7 */
8
9#include <linux/delay.h>
10#include <linux/io.h>
11#include <linux/phy/phy.h>
12#include "phy-samsung-usb2.h"
13
14/* Exynos USB PHY registers */
15
16/* PHY power control */
17#define S5PV210_UPHYPWR			0x0
18
19#define S5PV210_UPHYPWR_PHY0_SUSPEND	BIT(0)
20#define S5PV210_UPHYPWR_PHY0_PWR	BIT(3)
21#define S5PV210_UPHYPWR_PHY0_OTG_PWR	BIT(4)
22#define S5PV210_UPHYPWR_PHY0	( \
23	S5PV210_UPHYPWR_PHY0_SUSPEND | \
24	S5PV210_UPHYPWR_PHY0_PWR | \
25	S5PV210_UPHYPWR_PHY0_OTG_PWR)
26
27#define S5PV210_UPHYPWR_PHY1_SUSPEND	BIT(6)
28#define S5PV210_UPHYPWR_PHY1_PWR	BIT(7)
29#define S5PV210_UPHYPWR_PHY1 ( \
30	S5PV210_UPHYPWR_PHY1_SUSPEND | \
31	S5PV210_UPHYPWR_PHY1_PWR)
32
33/* PHY clock control */
34#define S5PV210_UPHYCLK			0x4
35
36#define S5PV210_UPHYCLK_PHYFSEL_MASK	(0x3 << 0)
37#define S5PV210_UPHYCLK_PHYFSEL_48MHZ	(0x0 << 0)
38#define S5PV210_UPHYCLK_PHYFSEL_24MHZ	(0x3 << 0)
39#define S5PV210_UPHYCLK_PHYFSEL_12MHZ	(0x2 << 0)
40
41#define S5PV210_UPHYCLK_PHY0_ID_PULLUP	BIT(2)
42#define S5PV210_UPHYCLK_PHY0_COMMON_ON	BIT(4)
43#define S5PV210_UPHYCLK_PHY1_COMMON_ON	BIT(7)
44
45/* PHY reset control */
46#define S5PV210_UPHYRST			0x8
47
48#define S5PV210_URSTCON_PHY0		BIT(0)
49#define S5PV210_URSTCON_OTG_HLINK	BIT(1)
50#define S5PV210_URSTCON_OTG_PHYLINK	BIT(2)
51#define S5PV210_URSTCON_PHY1_ALL	BIT(3)
52#define S5PV210_URSTCON_HOST_LINK_ALL	BIT(4)
53
54/* Isolation, configured in the power management unit */
55#define S5PV210_USB_ISOL_OFFSET		0x680c
56#define S5PV210_USB_ISOL_DEVICE		BIT(0)
57#define S5PV210_USB_ISOL_HOST		BIT(1)
58
59
60enum s5pv210_phy_id {
61	S5PV210_DEVICE,
62	S5PV210_HOST,
63	S5PV210_NUM_PHYS,
64};
65
66/*
67 * s5pv210_rate_to_clk() converts the supplied clock rate to the value that
68 * can be written to the phy register.
69 */
70static int s5pv210_rate_to_clk(unsigned long rate, u32 *reg)
71{
72	switch (rate) {
73	case 12 * MHZ:
74		*reg = S5PV210_UPHYCLK_PHYFSEL_12MHZ;
75		break;
76	case 24 * MHZ:
77		*reg = S5PV210_UPHYCLK_PHYFSEL_24MHZ;
78		break;
79	case 48 * MHZ:
80		*reg = S5PV210_UPHYCLK_PHYFSEL_48MHZ;
81		break;
82	default:
83		return -EINVAL;
84	}
85
86	return 0;
87}
88
89static void s5pv210_isol(struct samsung_usb2_phy_instance *inst, bool on)
90{
91	struct samsung_usb2_phy_driver *drv = inst->drv;
92	u32 mask;
93
94	switch (inst->cfg->id) {
95	case S5PV210_DEVICE:
96		mask = S5PV210_USB_ISOL_DEVICE;
97		break;
98	case S5PV210_HOST:
99		mask = S5PV210_USB_ISOL_HOST;
100		break;
101	default:
102		return;
103	}
104
105	regmap_update_bits(drv->reg_pmu, S5PV210_USB_ISOL_OFFSET,
106							mask, on ? 0 : mask);
107}
108
109static void s5pv210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on)
110{
111	struct samsung_usb2_phy_driver *drv = inst->drv;
112	u32 rstbits = 0;
113	u32 phypwr = 0;
114	u32 rst;
115	u32 pwr;
116
117	switch (inst->cfg->id) {
118	case S5PV210_DEVICE:
119		phypwr =	S5PV210_UPHYPWR_PHY0;
120		rstbits =	S5PV210_URSTCON_PHY0;
121		break;
122	case S5PV210_HOST:
123		phypwr =	S5PV210_UPHYPWR_PHY1;
124		rstbits =	S5PV210_URSTCON_PHY1_ALL |
125				S5PV210_URSTCON_HOST_LINK_ALL;
126		break;
127	}
128
129	if (on) {
130		writel(drv->ref_reg_val, drv->reg_phy + S5PV210_UPHYCLK);
131
132		pwr = readl(drv->reg_phy + S5PV210_UPHYPWR);
133		pwr &= ~phypwr;
134		writel(pwr, drv->reg_phy + S5PV210_UPHYPWR);
135
136		rst = readl(drv->reg_phy + S5PV210_UPHYRST);
137		rst |= rstbits;
138		writel(rst, drv->reg_phy + S5PV210_UPHYRST);
139		udelay(10);
140		rst &= ~rstbits;
141		writel(rst, drv->reg_phy + S5PV210_UPHYRST);
142		/* The following delay is necessary for the reset sequence to be
143		 * completed
144		 */
145		udelay(80);
146	} else {
147		pwr = readl(drv->reg_phy + S5PV210_UPHYPWR);
148		pwr |= phypwr;
149		writel(pwr, drv->reg_phy + S5PV210_UPHYPWR);
150	}
151}
152
153static int s5pv210_power_on(struct samsung_usb2_phy_instance *inst)
154{
155	s5pv210_isol(inst, 0);
156	s5pv210_phy_pwr(inst, 1);
157
158	return 0;
159}
160
161static int s5pv210_power_off(struct samsung_usb2_phy_instance *inst)
162{
163	s5pv210_phy_pwr(inst, 0);
164	s5pv210_isol(inst, 1);
165
166	return 0;
167}
168
169static const struct samsung_usb2_common_phy s5pv210_phys[S5PV210_NUM_PHYS] = {
170	[S5PV210_DEVICE] = {
171		.label		= "device",
172		.id		= S5PV210_DEVICE,
173		.power_on	= s5pv210_power_on,
174		.power_off	= s5pv210_power_off,
175	},
176	[S5PV210_HOST] = {
177		.label		= "host",
178		.id		= S5PV210_HOST,
179		.power_on	= s5pv210_power_on,
180		.power_off	= s5pv210_power_off,
181	},
182};
183
184const struct samsung_usb2_phy_config s5pv210_usb2_phy_config = {
185	.num_phys	= ARRAY_SIZE(s5pv210_phys),
186	.phys		= s5pv210_phys,
187	.rate_to_clk	= s5pv210_rate_to_clk,
188};
189