Skip to content

Commit 133add5

Browse files
committed
drm/sun4i: Add Allwinner A31 MIPI-DSI controller support
Most of the Allwinner SoCs since the A31 share the same MIPI-DSI controller. While that controller is mostly undocumented, the code is out there and has been cleaned up in order to be integrated into DRM. However, there's still some dark areas that are a bit unclear about how the block exactly operates. Reviewed-by: Chen-Yu Tsai <[email protected]> Signed-off-by: Maxime Ripard <[email protected]> Link: https://patchwork.freedesktop.org/patch/msgid/ad9e6224fced87c0889ddd2765d1942610061f72.1522835818.git-series.maxime.ripard@bootlin.com
1 parent 7605225 commit 133add5

File tree

5 files changed

+1476
-0
lines changed

5 files changed

+1476
-0
lines changed

drivers/gpu/drm/sun4i/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ config DRM_SUN4I_BACKEND
4040
do some alpha blending and feed graphics to TCON. If M is
4141
selected the module will be called sun4i-backend.
4242

43+
config DRM_SUN6I_DSI
44+
tristate "Allwinner A31 MIPI-DSI Controller Support"
45+
default MACH_SUN8I
46+
select CRC_CCITT
47+
select DRM_MIPI_DSI
48+
help
49+
Choose this option if you want have an Allwinner SoC with
50+
MIPI-DSI support. If M is selected the module will be called
51+
sun6i-dsi
52+
4353
config DRM_SUN8I_DW_HDMI
4454
tristate "Support for Allwinner version of DesignWare HDMI"
4555
depends on DRM_SUN4I

drivers/gpu/drm/sun4i/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,16 @@ sun4i-tcon-y += sun4i_lvds.o
2424
sun4i-tcon-y += sun4i_tcon.o
2525
sun4i-tcon-y += sun4i_rgb.o
2626

27+
sun6i-dsi-y += sun6i_mipi_dphy.o
28+
sun6i-dsi-y += sun6i_mipi_dsi.o
29+
2730
obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o
2831
obj-$(CONFIG_DRM_SUN4I) += sun4i-tcon.o
2932
obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o
3033
obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o
3134

3235
obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o sun4i-frontend.o
3336
obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o
37+
obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i-dsi.o
3438
obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i-drm-hdmi.o
3539
obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
// SPDX-License-Identifier: GPL-2.0+
2+
/*
3+
* Copyright (c) 2016 Allwinnertech Co., Ltd.
4+
* Copyright (C) 2017-2018 Bootlin
5+
*
6+
* Maxime Ripard <[email protected]>
7+
*/
8+
9+
#include <linux/bitops.h>
10+
#include <linux/clk.h>
11+
#include <linux/of_address.h>
12+
#include <linux/regmap.h>
13+
#include <linux/reset.h>
14+
15+
#include "sun6i_mipi_dsi.h"
16+
17+
#define SUN6I_DPHY_GCTL_REG 0x00
18+
#define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4)
19+
#define SUN6I_DPHY_GCTL_EN BIT(0)
20+
21+
#define SUN6I_DPHY_TX_CTL_REG 0x04
22+
#define SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT BIT(28)
23+
24+
#define SUN6I_DPHY_TX_TIME0_REG 0x10
25+
#define SUN6I_DPHY_TX_TIME0_HS_TRAIL(n) (((n) & 0xff) << 24)
26+
#define SUN6I_DPHY_TX_TIME0_HS_PREPARE(n) (((n) & 0xff) << 16)
27+
#define SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(n) ((n) & 0xff)
28+
29+
#define SUN6I_DPHY_TX_TIME1_REG 0x14
30+
#define SUN6I_DPHY_TX_TIME1_CLK_POST(n) (((n) & 0xff) << 24)
31+
#define SUN6I_DPHY_TX_TIME1_CLK_PRE(n) (((n) & 0xff) << 16)
32+
#define SUN6I_DPHY_TX_TIME1_CLK_ZERO(n) (((n) & 0xff) << 8)
33+
#define SUN6I_DPHY_TX_TIME1_CLK_PREPARE(n) ((n) & 0xff)
34+
35+
#define SUN6I_DPHY_TX_TIME2_REG 0x18
36+
#define SUN6I_DPHY_TX_TIME2_CLK_TRAIL(n) ((n) & 0xff)
37+
38+
#define SUN6I_DPHY_TX_TIME3_REG 0x1c
39+
40+
#define SUN6I_DPHY_TX_TIME4_REG 0x20
41+
#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(n) (((n) & 0xff) << 8)
42+
#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(n) ((n) & 0xff)
43+
44+
#define SUN6I_DPHY_ANA0_REG 0x4c
45+
#define SUN6I_DPHY_ANA0_REG_PWS BIT(31)
46+
#define SUN6I_DPHY_ANA0_REG_DMPC BIT(28)
47+
#define SUN6I_DPHY_ANA0_REG_DMPD(n) (((n) & 0xf) << 24)
48+
#define SUN6I_DPHY_ANA0_REG_SLV(n) (((n) & 7) << 12)
49+
#define SUN6I_DPHY_ANA0_REG_DEN(n) (((n) & 0xf) << 8)
50+
51+
#define SUN6I_DPHY_ANA1_REG 0x50
52+
#define SUN6I_DPHY_ANA1_REG_VTTMODE BIT(31)
53+
#define SUN6I_DPHY_ANA1_REG_CSMPS(n) (((n) & 3) << 28)
54+
#define SUN6I_DPHY_ANA1_REG_SVTT(n) (((n) & 0xf) << 24)
55+
56+
#define SUN6I_DPHY_ANA2_REG 0x54
57+
#define SUN6I_DPHY_ANA2_EN_P2S_CPU(n) (((n) & 0xf) << 24)
58+
#define SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK GENMASK(27, 24)
59+
#define SUN6I_DPHY_ANA2_EN_CK_CPU BIT(4)
60+
#define SUN6I_DPHY_ANA2_REG_ENIB BIT(1)
61+
62+
#define SUN6I_DPHY_ANA3_REG 0x58
63+
#define SUN6I_DPHY_ANA3_EN_VTTD(n) (((n) & 0xf) << 28)
64+
#define SUN6I_DPHY_ANA3_EN_VTTD_MASK GENMASK(31, 28)
65+
#define SUN6I_DPHY_ANA3_EN_VTTC BIT(27)
66+
#define SUN6I_DPHY_ANA3_EN_DIV BIT(26)
67+
#define SUN6I_DPHY_ANA3_EN_LDOC BIT(25)
68+
#define SUN6I_DPHY_ANA3_EN_LDOD BIT(24)
69+
#define SUN6I_DPHY_ANA3_EN_LDOR BIT(18)
70+
71+
#define SUN6I_DPHY_ANA4_REG 0x5c
72+
#define SUN6I_DPHY_ANA4_REG_DMPLVC BIT(24)
73+
#define SUN6I_DPHY_ANA4_REG_DMPLVD(n) (((n) & 0xf) << 20)
74+
#define SUN6I_DPHY_ANA4_REG_CKDV(n) (((n) & 0x1f) << 12)
75+
#define SUN6I_DPHY_ANA4_REG_TMSC(n) (((n) & 3) << 10)
76+
#define SUN6I_DPHY_ANA4_REG_TMSD(n) (((n) & 3) << 8)
77+
#define SUN6I_DPHY_ANA4_REG_TXDNSC(n) (((n) & 3) << 6)
78+
#define SUN6I_DPHY_ANA4_REG_TXDNSD(n) (((n) & 3) << 4)
79+
#define SUN6I_DPHY_ANA4_REG_TXPUSC(n) (((n) & 3) << 2)
80+
#define SUN6I_DPHY_ANA4_REG_TXPUSD(n) ((n) & 3)
81+
82+
#define SUN6I_DPHY_DBG5_REG 0xf4
83+
84+
int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes)
85+
{
86+
reset_control_deassert(dphy->reset);
87+
clk_prepare_enable(dphy->mod_clk);
88+
clk_set_rate_exclusive(dphy->mod_clk, 150000000);
89+
90+
regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG,
91+
SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT);
92+
93+
regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME0_REG,
94+
SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(14) |
95+
SUN6I_DPHY_TX_TIME0_HS_PREPARE(6) |
96+
SUN6I_DPHY_TX_TIME0_HS_TRAIL(10));
97+
98+
regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME1_REG,
99+
SUN6I_DPHY_TX_TIME1_CLK_PREPARE(7) |
100+
SUN6I_DPHY_TX_TIME1_CLK_ZERO(50) |
101+
SUN6I_DPHY_TX_TIME1_CLK_PRE(3) |
102+
SUN6I_DPHY_TX_TIME1_CLK_POST(10));
103+
104+
regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME2_REG,
105+
SUN6I_DPHY_TX_TIME2_CLK_TRAIL(30));
106+
107+
regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME3_REG, 0);
108+
109+
regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME4_REG,
110+
SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(3) |
111+
SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3));
112+
113+
regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG,
114+
SUN6I_DPHY_GCTL_LANE_NUM(lanes) |
115+
SUN6I_DPHY_GCTL_EN);
116+
117+
return 0;
118+
}
119+
120+
int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes)
121+
{
122+
u8 lanes_mask = GENMASK(lanes - 1, 0);
123+
124+
regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG,
125+
SUN6I_DPHY_ANA0_REG_PWS |
126+
SUN6I_DPHY_ANA0_REG_DMPC |
127+
SUN6I_DPHY_ANA0_REG_SLV(7) |
128+
SUN6I_DPHY_ANA0_REG_DMPD(lanes_mask) |
129+
SUN6I_DPHY_ANA0_REG_DEN(lanes_mask));
130+
131+
regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG,
132+
SUN6I_DPHY_ANA1_REG_CSMPS(1) |
133+
SUN6I_DPHY_ANA1_REG_SVTT(7));
134+
135+
regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG,
136+
SUN6I_DPHY_ANA4_REG_CKDV(1) |
137+
SUN6I_DPHY_ANA4_REG_TMSC(1) |
138+
SUN6I_DPHY_ANA4_REG_TMSD(1) |
139+
SUN6I_DPHY_ANA4_REG_TXDNSC(1) |
140+
SUN6I_DPHY_ANA4_REG_TXDNSD(1) |
141+
SUN6I_DPHY_ANA4_REG_TXPUSC(1) |
142+
SUN6I_DPHY_ANA4_REG_TXPUSD(1) |
143+
SUN6I_DPHY_ANA4_REG_DMPLVC |
144+
SUN6I_DPHY_ANA4_REG_DMPLVD(lanes_mask));
145+
146+
regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG,
147+
SUN6I_DPHY_ANA2_REG_ENIB);
148+
udelay(5);
149+
150+
regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG,
151+
SUN6I_DPHY_ANA3_EN_LDOR |
152+
SUN6I_DPHY_ANA3_EN_LDOC |
153+
SUN6I_DPHY_ANA3_EN_LDOD);
154+
udelay(1);
155+
156+
regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG,
157+
SUN6I_DPHY_ANA3_EN_VTTC |
158+
SUN6I_DPHY_ANA3_EN_VTTD_MASK,
159+
SUN6I_DPHY_ANA3_EN_VTTC |
160+
SUN6I_DPHY_ANA3_EN_VTTD(lanes_mask));
161+
udelay(1);
162+
163+
regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG,
164+
SUN6I_DPHY_ANA3_EN_DIV,
165+
SUN6I_DPHY_ANA3_EN_DIV);
166+
udelay(1);
167+
168+
regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG,
169+
SUN6I_DPHY_ANA2_EN_CK_CPU,
170+
SUN6I_DPHY_ANA2_EN_CK_CPU);
171+
udelay(1);
172+
173+
regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG,
174+
SUN6I_DPHY_ANA1_REG_VTTMODE,
175+
SUN6I_DPHY_ANA1_REG_VTTMODE);
176+
177+
regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG,
178+
SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK,
179+
SUN6I_DPHY_ANA2_EN_P2S_CPU(lanes_mask));
180+
181+
return 0;
182+
}
183+
184+
int sun6i_dphy_power_off(struct sun6i_dphy *dphy)
185+
{
186+
regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG,
187+
SUN6I_DPHY_ANA1_REG_VTTMODE, 0);
188+
189+
return 0;
190+
}
191+
192+
int sun6i_dphy_exit(struct sun6i_dphy *dphy)
193+
{
194+
clk_rate_exclusive_put(dphy->mod_clk);
195+
clk_disable_unprepare(dphy->mod_clk);
196+
reset_control_assert(dphy->reset);
197+
198+
return 0;
199+
}
200+
201+
static struct regmap_config sun6i_dphy_regmap_config = {
202+
.reg_bits = 32,
203+
.val_bits = 32,
204+
.reg_stride = 4,
205+
.max_register = SUN6I_DPHY_DBG5_REG,
206+
.name = "mipi-dphy",
207+
};
208+
209+
static const struct of_device_id sun6i_dphy_of_table[] = {
210+
{ .compatible = "allwinner,sun6i-a31-mipi-dphy" },
211+
{ }
212+
};
213+
214+
int sun6i_dphy_probe(struct sun6i_dsi *dsi, struct device_node *node)
215+
{
216+
struct sun6i_dphy *dphy;
217+
struct resource res;
218+
void __iomem *regs;
219+
int ret;
220+
221+
if (!of_match_node(sun6i_dphy_of_table, node)) {
222+
dev_err(dsi->dev, "Incompatible D-PHY\n");
223+
return -EINVAL;
224+
}
225+
226+
dphy = devm_kzalloc(dsi->dev, sizeof(*dphy), GFP_KERNEL);
227+
if (!dphy)
228+
return -ENOMEM;
229+
230+
ret = of_address_to_resource(node, 0, &res);
231+
if (ret) {
232+
dev_err(dsi->dev, "phy: Couldn't get our resources\n");
233+
return ret;
234+
}
235+
236+
regs = devm_ioremap_resource(dsi->dev, &res);
237+
if (IS_ERR(regs)) {
238+
dev_err(dsi->dev, "Couldn't map the DPHY encoder registers\n");
239+
return PTR_ERR(regs);
240+
}
241+
242+
dphy->regs = devm_regmap_init_mmio(dsi->dev, regs,
243+
&sun6i_dphy_regmap_config);
244+
if (IS_ERR(dphy->regs)) {
245+
dev_err(dsi->dev, "Couldn't create the DPHY encoder regmap\n");
246+
return PTR_ERR(dphy->regs);
247+
}
248+
249+
dphy->reset = of_reset_control_get_shared(node, NULL);
250+
if (IS_ERR(dphy->reset)) {
251+
dev_err(dsi->dev, "Couldn't get our reset line\n");
252+
return PTR_ERR(dphy->reset);
253+
}
254+
255+
dphy->bus_clk = of_clk_get_by_name(node, "bus");
256+
if (IS_ERR(dphy->bus_clk)) {
257+
dev_err(dsi->dev, "Couldn't get the DPHY bus clock\n");
258+
ret = PTR_ERR(dphy->bus_clk);
259+
goto err_free_reset;
260+
}
261+
regmap_mmio_attach_clk(dphy->regs, dphy->bus_clk);
262+
263+
dphy->mod_clk = of_clk_get_by_name(node, "mod");
264+
if (IS_ERR(dphy->mod_clk)) {
265+
dev_err(dsi->dev, "Couldn't get the DPHY mod clock\n");
266+
ret = PTR_ERR(dphy->mod_clk);
267+
goto err_free_bus;
268+
}
269+
270+
dsi->dphy = dphy;
271+
272+
return 0;
273+
274+
err_free_bus:
275+
regmap_mmio_detach_clk(dphy->regs);
276+
clk_put(dphy->bus_clk);
277+
err_free_reset:
278+
reset_control_put(dphy->reset);
279+
return ret;
280+
}
281+
282+
int sun6i_dphy_remove(struct sun6i_dsi *dsi)
283+
{
284+
struct sun6i_dphy *dphy = dsi->dphy;
285+
286+
regmap_mmio_detach_clk(dphy->regs);
287+
clk_put(dphy->mod_clk);
288+
clk_put(dphy->bus_clk);
289+
reset_control_put(dphy->reset);
290+
291+
return 0;
292+
}

0 commit comments

Comments
 (0)