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