• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
s5pv210_rate_to_clk(unsigned long rate,u32 * reg)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 
s5pv210_isol(struct samsung_usb2_phy_instance * inst,bool on)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 
s5pv210_phy_pwr(struct samsung_usb2_phy_instance * inst,bool on)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 		/* The following delay is necessary for the reset sequence to be
146 		 * completed
147 		 */
148 		udelay(80);
149 	} else {
150 		pwr = readl(drv->reg_phy + S5PV210_UPHYPWR);
151 		pwr |= phypwr;
152 		writel(pwr, drv->reg_phy + S5PV210_UPHYPWR);
153 	}
154 }
155 
s5pv210_power_on(struct samsung_usb2_phy_instance * inst)156 static int s5pv210_power_on(struct samsung_usb2_phy_instance *inst)
157 {
158 	s5pv210_isol(inst, 0);
159 	s5pv210_phy_pwr(inst, 1);
160 
161 	return 0;
162 }
163 
s5pv210_power_off(struct samsung_usb2_phy_instance * inst)164 static int s5pv210_power_off(struct samsung_usb2_phy_instance *inst)
165 {
166 	s5pv210_phy_pwr(inst, 0);
167 	s5pv210_isol(inst, 1);
168 
169 	return 0;
170 }
171 
172 static const struct samsung_usb2_common_phy s5pv210_phys[S5PV210_NUM_PHYS] = {
173 	[S5PV210_DEVICE] = {
174 		.label		= "device",
175 		.id		= S5PV210_DEVICE,
176 		.power_on	= s5pv210_power_on,
177 		.power_off	= s5pv210_power_off,
178 	},
179 	[S5PV210_HOST] = {
180 		.label		= "host",
181 		.id		= S5PV210_HOST,
182 		.power_on	= s5pv210_power_on,
183 		.power_off	= s5pv210_power_off,
184 	},
185 };
186 
187 const struct samsung_usb2_phy_config s5pv210_usb2_phy_config = {
188 	.num_phys	= ARRAY_SIZE(s5pv210_phys),
189 	.phys		= s5pv210_phys,
190 	.rate_to_clk	= s5pv210_rate_to_clk,
191 };
192