diff options
author | Shawn Lin <shawn.lin@rock-chips.com> | 2016-02-03 02:22:22 -0500 |
---|---|---|
committer | Kishon Vijay Abraham I <kishon@ti.com> | 2016-03-01 06:43:50 -0500 |
commit | c474a949508055d14e867f107074806fa8fa17ad (patch) | |
tree | a0369035639c01cfe5c157827347258f23f10f53 /drivers/phy/phy-rockchip-emmc.c | |
parent | 7ed0c9613bcb959f4dd0cd7e44e320e94b6f44de (diff) |
phy: add a driver for the Rockchip SoC internal eMMC PHY
This patch to add a generic PHY driver for ROCKCHIP eMMC PHY.
Access the PHY via registers provided by GRF (general register
files) module.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
Diffstat (limited to 'drivers/phy/phy-rockchip-emmc.c')
-rw-r--r-- | drivers/phy/phy-rockchip-emmc.c | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/drivers/phy/phy-rockchip-emmc.c b/drivers/phy/phy-rockchip-emmc.c new file mode 100644 index 000000000000..887b4c27195f --- /dev/null +++ b/drivers/phy/phy-rockchip-emmc.c | |||
@@ -0,0 +1,229 @@ | |||
1 | /* | ||
2 | * Rockchip emmc PHY driver | ||
3 | * | ||
4 | * Copyright (C) 2016 Shawn Lin <shawn.lin@rock-chips.com> | ||
5 | * Copyright (C) 2016 ROCKCHIP, Inc. | ||
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 as published by | ||
9 | * the Free Software Foundation; either version 2 of the License. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | */ | ||
16 | |||
17 | #include <linux/delay.h> | ||
18 | #include <linux/mfd/syscon.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/of.h> | ||
21 | #include <linux/of_address.h> | ||
22 | #include <linux/phy/phy.h> | ||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/regmap.h> | ||
25 | |||
26 | /* | ||
27 | * The higher 16-bit of this register is used for write protection | ||
28 | * only if BIT(x + 16) set to 1 the BIT(x) can be written. | ||
29 | */ | ||
30 | #define HIWORD_UPDATE(val, mask, shift) \ | ||
31 | ((val) << (shift) | (mask) << ((shift) + 16)) | ||
32 | |||
33 | /* Register definition */ | ||
34 | #define GRF_EMMCPHY_CON0 0x0 | ||
35 | #define GRF_EMMCPHY_CON1 0x4 | ||
36 | #define GRF_EMMCPHY_CON2 0x8 | ||
37 | #define GRF_EMMCPHY_CON3 0xc | ||
38 | #define GRF_EMMCPHY_CON4 0x10 | ||
39 | #define GRF_EMMCPHY_CON5 0x14 | ||
40 | #define GRF_EMMCPHY_CON6 0x18 | ||
41 | #define GRF_EMMCPHY_STATUS 0x20 | ||
42 | |||
43 | #define PHYCTRL_PDB_MASK 0x1 | ||
44 | #define PHYCTRL_PDB_SHIFT 0x0 | ||
45 | #define PHYCTRL_PDB_PWR_ON 0x1 | ||
46 | #define PHYCTRL_PDB_PWR_OFF 0x0 | ||
47 | #define PHYCTRL_ENDLL_MASK 0x1 | ||
48 | #define PHYCTRL_ENDLL_SHIFT 0x1 | ||
49 | #define PHYCTRL_ENDLL_ENABLE 0x1 | ||
50 | #define PHYCTRL_ENDLL_DISABLE 0x0 | ||
51 | #define PHYCTRL_CALDONE_MASK 0x1 | ||
52 | #define PHYCTRL_CALDONE_SHIFT 0x6 | ||
53 | #define PHYCTRL_CALDONE_DONE 0x1 | ||
54 | #define PHYCTRL_CALDONE_GOING 0x0 | ||
55 | #define PHYCTRL_DLLRDY_MASK 0x1 | ||
56 | #define PHYCTRL_DLLRDY_SHIFT 0x5 | ||
57 | #define PHYCTRL_DLLRDY_DONE 0x1 | ||
58 | #define PHYCTRL_DLLRDY_GOING 0x0 | ||
59 | |||
60 | struct rockchip_emmc_phy { | ||
61 | unsigned int reg_offset; | ||
62 | struct regmap *reg_base; | ||
63 | }; | ||
64 | |||
65 | static int rockchip_emmc_phy_power(struct rockchip_emmc_phy *rk_phy, | ||
66 | bool on_off) | ||
67 | { | ||
68 | unsigned int caldone; | ||
69 | unsigned int dllrdy; | ||
70 | |||
71 | /* | ||
72 | * Keep phyctrl_pdb and phyctrl_endll low to allow | ||
73 | * initialization of CALIO state M/C DFFs | ||
74 | */ | ||
75 | regmap_write(rk_phy->reg_base, | ||
76 | rk_phy->reg_offset + GRF_EMMCPHY_CON6, | ||
77 | HIWORD_UPDATE(PHYCTRL_PDB_PWR_OFF, | ||
78 | PHYCTRL_PDB_MASK, | ||
79 | PHYCTRL_PDB_SHIFT)); | ||
80 | regmap_write(rk_phy->reg_base, | ||
81 | rk_phy->reg_offset + GRF_EMMCPHY_CON6, | ||
82 | HIWORD_UPDATE(PHYCTRL_ENDLL_DISABLE, | ||
83 | PHYCTRL_ENDLL_MASK, | ||
84 | PHYCTRL_ENDLL_SHIFT)); | ||
85 | |||
86 | /* Already finish power_off above */ | ||
87 | if (on_off == PHYCTRL_PDB_PWR_OFF) | ||
88 | return 0; | ||
89 | |||
90 | /* | ||
91 | * According to the user manual, calpad calibration | ||
92 | * cycle takes more than 2us without the minimal recommended | ||
93 | * value, so we may need a little margin here | ||
94 | */ | ||
95 | udelay(3); | ||
96 | regmap_write(rk_phy->reg_base, | ||
97 | rk_phy->reg_offset + GRF_EMMCPHY_CON6, | ||
98 | HIWORD_UPDATE(PHYCTRL_PDB_PWR_ON, | ||
99 | PHYCTRL_PDB_MASK, | ||
100 | PHYCTRL_PDB_SHIFT)); | ||
101 | |||
102 | /* | ||
103 | * According to the user manual, it asks driver to | ||
104 | * wait 5us for calpad busy trimming | ||
105 | */ | ||
106 | udelay(5); | ||
107 | regmap_read(rk_phy->reg_base, | ||
108 | rk_phy->reg_offset + GRF_EMMCPHY_STATUS, | ||
109 | &caldone); | ||
110 | caldone = (caldone >> PHYCTRL_CALDONE_SHIFT) & PHYCTRL_CALDONE_MASK; | ||
111 | if (caldone != PHYCTRL_CALDONE_DONE) { | ||
112 | pr_err("rockchip_emmc_phy_power: caldone timeout.\n"); | ||
113 | return -ETIMEDOUT; | ||
114 | } | ||
115 | |||
116 | regmap_write(rk_phy->reg_base, | ||
117 | rk_phy->reg_offset + GRF_EMMCPHY_CON6, | ||
118 | HIWORD_UPDATE(PHYCTRL_ENDLL_ENABLE, | ||
119 | PHYCTRL_ENDLL_MASK, | ||
120 | PHYCTRL_ENDLL_SHIFT)); | ||
121 | /* | ||
122 | * After enable analog DLL circuits, we need extra 10.2us | ||
123 | * for dll to be ready for work. | ||
124 | */ | ||
125 | udelay(11); | ||
126 | regmap_read(rk_phy->reg_base, | ||
127 | rk_phy->reg_offset + GRF_EMMCPHY_STATUS, | ||
128 | &dllrdy); | ||
129 | dllrdy = (dllrdy >> PHYCTRL_DLLRDY_SHIFT) & PHYCTRL_DLLRDY_MASK; | ||
130 | if (dllrdy != PHYCTRL_DLLRDY_DONE) { | ||
131 | pr_err("rockchip_emmc_phy_power: dllrdy timeout.\n"); | ||
132 | return -ETIMEDOUT; | ||
133 | } | ||
134 | |||
135 | return 0; | ||
136 | } | ||
137 | |||
138 | static int rockchip_emmc_phy_power_off(struct phy *phy) | ||
139 | { | ||
140 | struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); | ||
141 | int ret = 0; | ||
142 | |||
143 | /* Power down emmc phy analog blocks */ | ||
144 | ret = rockchip_emmc_phy_power(rk_phy, PHYCTRL_PDB_PWR_OFF); | ||
145 | if (ret) | ||
146 | return ret; | ||
147 | |||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | static int rockchip_emmc_phy_power_on(struct phy *phy) | ||
152 | { | ||
153 | struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); | ||
154 | int ret = 0; | ||
155 | |||
156 | /* Power up emmc phy analog blocks */ | ||
157 | ret = rockchip_emmc_phy_power(rk_phy, PHYCTRL_PDB_PWR_ON); | ||
158 | if (ret) | ||
159 | return ret; | ||
160 | |||
161 | return 0; | ||
162 | } | ||
163 | |||
164 | static const struct phy_ops ops = { | ||
165 | .power_on = rockchip_emmc_phy_power_on, | ||
166 | .power_off = rockchip_emmc_phy_power_off, | ||
167 | .owner = THIS_MODULE, | ||
168 | }; | ||
169 | |||
170 | static int rockchip_emmc_phy_probe(struct platform_device *pdev) | ||
171 | { | ||
172 | struct device *dev = &pdev->dev; | ||
173 | struct rockchip_emmc_phy *rk_phy; | ||
174 | struct phy *generic_phy; | ||
175 | struct phy_provider *phy_provider; | ||
176 | struct regmap *grf; | ||
177 | unsigned int reg_offset; | ||
178 | |||
179 | grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); | ||
180 | if (IS_ERR(grf)) { | ||
181 | dev_err(dev, "Missing rockchip,grf property\n"); | ||
182 | return PTR_ERR(grf); | ||
183 | } | ||
184 | |||
185 | rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); | ||
186 | if (!rk_phy) | ||
187 | return -ENOMEM; | ||
188 | |||
189 | if (of_property_read_u32(dev->of_node, "reg", ®_offset)) { | ||
190 | dev_err(dev, "missing reg property in node %s\n", | ||
191 | dev->of_node->name); | ||
192 | return -EINVAL; | ||
193 | } | ||
194 | |||
195 | rk_phy->reg_offset = reg_offset; | ||
196 | rk_phy->reg_base = grf; | ||
197 | |||
198 | generic_phy = devm_phy_create(dev, dev->of_node, &ops); | ||
199 | if (IS_ERR(generic_phy)) { | ||
200 | dev_err(dev, "failed to create PHY\n"); | ||
201 | return PTR_ERR(generic_phy); | ||
202 | } | ||
203 | |||
204 | phy_set_drvdata(generic_phy, rk_phy); | ||
205 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | ||
206 | |||
207 | return PTR_ERR_OR_ZERO(phy_provider); | ||
208 | } | ||
209 | |||
210 | static const struct of_device_id rockchip_emmc_phy_dt_ids[] = { | ||
211 | { .compatible = "rockchip,rk3399-emmc-phy" }, | ||
212 | {} | ||
213 | }; | ||
214 | |||
215 | MODULE_DEVICE_TABLE(of, rockchip_emmc_phy_dt_ids); | ||
216 | |||
217 | static struct platform_driver rockchip_emmc_driver = { | ||
218 | .probe = rockchip_emmc_phy_probe, | ||
219 | .driver = { | ||
220 | .name = "rockchip-emmc-phy", | ||
221 | .of_match_table = rockchip_emmc_phy_dt_ids, | ||
222 | }, | ||
223 | }; | ||
224 | |||
225 | module_platform_driver(rockchip_emmc_driver); | ||
226 | |||
227 | MODULE_AUTHOR("Shawn Lin <shawn.lin@rock-chips.com>"); | ||
228 | MODULE_DESCRIPTION("Rockchip EMMC PHY driver"); | ||
229 | MODULE_LICENSE("GPL v2"); | ||