GNU Linux-libre 4.4.289-gnu1
[releases.git] / drivers / phy / phy-s5pv210-usb2.c
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                 /* 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
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
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 };