1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2018 Rockchip Electronics Co. Ltd.
4 *
5 * Author: Wyon Bi <bivvy.bi@rock-chips.com>
6 */
7
8 #include <linux/kernel.h>
9 #include <linux/clk.h>
10 #include <linux/delay.h>
11 #include <linux/init.h>
12 #include <linux/module.h>
13 #include <linux/of_device.h>
14 #include <linux/platform_device.h>
15 #include <linux/regmap.h>
16 #include <linux/reset.h>
17 #include <linux/phy/phy.h>
18 #include <linux/pm_runtime.h>
19
20 /* 0x0030 */
21 #define DISABLE_PLL BIT(3)
22 /* 0x003c */
23 #define PLL_LOCK BIT(1)
24 /* 0x0084 */
25 #define ENABLE_TX BIT(7)
26
27 struct inno_video_phy {
28 struct device *dev;
29 struct clk *pclk;
30 struct regmap *regmap;
31 struct reset_control *rst;
32 };
33
34 static const struct reg_sequence ttl_mode[] = {
35 { 0x0000, 0x7f },
36 { 0x0004, 0x3f },
37 { 0x0008, 0x80 },
38 { 0x0010, 0x3f },
39 { 0x0014, 0x3f },
40 { 0x0080, 0x44 },
41
42 { 0x0100, 0x7f },
43 { 0x0104, 0x3f },
44 { 0x0108, 0x80 },
45 { 0x0110, 0x3f },
46 { 0x0114, 0x3f },
47 { 0x0180, 0x44 },
48 };
49
50 static const struct reg_sequence lvds_mode_single_channel[] = {
51 { 0x0000, 0xbf },
52 { 0x0004, 0x3f },
53 { 0x0008, 0xfe },
54 { 0x0010, 0x00 },
55 { 0x0014, 0x00 },
56 { 0x0080, 0x44 },
57
58 { 0x0100, 0x00 },
59 { 0x0104, 0x00 },
60 { 0x0108, 0x00 },
61 { 0x0110, 0x00 },
62 { 0x0114, 0x00 },
63 { 0x0180, 0x44 },
64 };
65
66 static const struct reg_sequence lvds_mode_dual_channel[] = {
67 { 0x0000, 0xbf },
68 { 0x0004, 0x3f },
69 { 0x0008, 0xfe },
70 { 0x0010, 0x00 },
71 { 0x0014, 0x00 },
72 { 0x0080, 0x44 },
73
74 { 0x0100, 0xbf },
75 { 0x0104, 0x3f },
76 { 0x0108, 0xfe },
77 { 0x0110, 0x00 },
78 { 0x0114, 0x00 },
79 { 0x0180, 0x44 },
80 };
81
inno_video_phy_power_on(struct phy * phy)82 static int inno_video_phy_power_on(struct phy *phy)
83 {
84 struct inno_video_phy *inno = phy_get_drvdata(phy);
85 enum phy_mode mode = phy_get_mode(phy);
86 const struct reg_sequence *wseq;
87 bool dual_channel = phy_get_bus_width(phy) == 2 ? true : false;
88 int nregs;
89 u32 status;
90 int ret;
91
92 clk_prepare_enable(inno->pclk);
93 pm_runtime_get_sync(inno->dev);
94
95 switch (mode) {
96 case PHY_MODE_LVDS:
97 if (dual_channel) {
98 wseq = lvds_mode_dual_channel;
99 nregs = ARRAY_SIZE(lvds_mode_dual_channel);
100 } else {
101 wseq = lvds_mode_single_channel;
102 nregs = ARRAY_SIZE(lvds_mode_single_channel);
103 }
104 break;
105 default:
106 wseq = ttl_mode;
107 nregs = ARRAY_SIZE(ttl_mode);
108 break;
109 }
110
111 regmap_multi_reg_write(inno->regmap, wseq, nregs);
112
113 regmap_update_bits(inno->regmap, 0x0030, DISABLE_PLL, 0);
114 ret = regmap_read_poll_timeout(inno->regmap, 0x003c, status,
115 status & PLL_LOCK, 50, 5000);
116 if (ret) {
117 dev_err(inno->dev, "PLL is not lock\n");
118 return ret;
119 }
120
121 regmap_update_bits(inno->regmap, 0x0084, ENABLE_TX, ENABLE_TX);
122
123 return 0;
124 }
125
inno_video_phy_power_off(struct phy * phy)126 static int inno_video_phy_power_off(struct phy *phy)
127 {
128 struct inno_video_phy *inno = phy_get_drvdata(phy);
129
130 regmap_update_bits(inno->regmap, 0x0084, ENABLE_TX, 0);
131 regmap_update_bits(inno->regmap, 0x0030, DISABLE_PLL, DISABLE_PLL);
132
133 pm_runtime_put(inno->dev);
134 clk_disable_unprepare(inno->pclk);
135
136 return 0;
137 }
138
inno_video_phy_set_mode(struct phy * phy,enum phy_mode mode)139 static int inno_video_phy_set_mode(struct phy *phy, enum phy_mode mode)
140 {
141 return 0;
142 }
143
144 static const struct phy_ops inno_video_phy_ops = {
145 .set_mode = inno_video_phy_set_mode,
146 .power_on = inno_video_phy_power_on,
147 .power_off = inno_video_phy_power_off,
148 .owner = THIS_MODULE,
149 };
150
151 static const struct regmap_config inno_video_phy_regmap_config = {
152 .reg_bits = 32,
153 .val_bits = 32,
154 .reg_stride = 4,
155 .max_register = 0x0180,
156 };
157
inno_video_phy_probe(struct platform_device * pdev)158 static int inno_video_phy_probe(struct platform_device *pdev)
159 {
160 struct device *dev = &pdev->dev;
161 struct inno_video_phy *inno;
162 struct phy *phy;
163 struct phy_provider *phy_provider;
164 struct resource *res;
165 void __iomem *regs;
166 int ret;
167
168 inno = devm_kzalloc(dev, sizeof(*inno), GFP_KERNEL);
169 if (!inno)
170 return -ENOMEM;
171
172 inno->dev = dev;
173 platform_set_drvdata(pdev, inno);
174
175 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
176 regs = devm_ioremap_resource(dev, res);
177 if (IS_ERR(regs))
178 return PTR_ERR(regs);
179
180 inno->regmap = devm_regmap_init_mmio(dev, regs,
181 &inno_video_phy_regmap_config);
182 if (IS_ERR(inno->regmap)) {
183 ret = PTR_ERR(inno->regmap);
184 dev_err(dev, "failed to init regmap: %d\n", ret);
185 return ret;
186 }
187
188 inno->pclk = devm_clk_get(dev, "pclk");
189 if (IS_ERR(inno->pclk)) {
190 dev_err(dev, "failed to get pclk\n");
191 return PTR_ERR(inno->pclk);
192 }
193
194 inno->rst = devm_reset_control_get(dev, "rst");
195 if (IS_ERR(inno->rst)) {
196 dev_err(dev, "failed to get reset control\n");
197 return PTR_ERR(inno->rst);
198 }
199
200 phy = devm_phy_create(dev, NULL, &inno_video_phy_ops);
201 if (IS_ERR(phy)) {
202 ret = PTR_ERR(phy);
203 dev_err(dev, "failed to create PHY: %d\n", ret);
204 return ret;
205 }
206
207 phy_set_drvdata(phy, inno);
208
209 phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
210 if (IS_ERR(phy_provider)) {
211 dev_err(dev, "failed to register phy provider\n");
212 return PTR_ERR(phy_provider);
213 }
214
215 pm_runtime_enable(dev);
216
217 return 0;
218 }
219
inno_video_phy_remove(struct platform_device * pdev)220 static int inno_video_phy_remove(struct platform_device *pdev)
221 {
222 pm_runtime_disable(&pdev->dev);
223
224 return 0;
225 }
226
227 static const struct of_device_id inno_video_phy_of_match[] = {
228 { .compatible = "rockchip,rk3288-video-phy", },
229 {}
230 };
231 MODULE_DEVICE_TABLE(of, inno_video_phy_of_match);
232
233 static struct platform_driver inno_video_phy_driver = {
234 .driver = {
235 .name = "inno-video-phy",
236 .of_match_table = of_match_ptr(inno_video_phy_of_match),
237 },
238 .probe = inno_video_phy_probe,
239 .remove = inno_video_phy_remove,
240 };
241 module_platform_driver(inno_video_phy_driver);
242
243 MODULE_AUTHOR("Wyon Bi <bivvy.bi@rock-chips.com>");
244 MODULE_DESCRIPTION("Innosilicon LVDS/TTL PHY driver");
245 MODULE_LICENSE("GPL v2");
246