diff options
author | Jernej Skrabec <jernej.skrabec@siol.net> | 2018-02-14 15:09:04 -0500 |
---|---|---|
committer | Maxime Ripard <maxime.ripard@bootlin.com> | 2018-02-16 03:38:30 -0500 |
commit | b7c7436a5ff0dd6a37de16310a7154cbfaca3a64 (patch) | |
tree | 98d4b862213d699400cc99ddc28b480704b0907b | |
parent | 47095e1635aa17a94bb1decdba3de1e21955d379 (diff) |
drm/sun4i: Implement A83T HDMI driver
A83T has DW HDMI IP block with a custom PHY similar to Synopsys gen2
HDMI PHY.
Only video output was tested, while HW also supports audio and CEC.
Support for them will be added later.
Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Signed-off-by: Maxime Ripard <maxime.ripard@bootlin.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20180214200906.31509-11-jernej.skrabec@siol.net
-rw-r--r-- | drivers/gpu/drm/sun4i/Kconfig | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/Makefile | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c | 196 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h | 44 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 270 |
5 files changed, 523 insertions, 0 deletions
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 882d85db9053..7327da3bc94f 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig | |||
@@ -40,6 +40,15 @@ config DRM_SUN4I_BACKEND | |||
40 | do some alpha blending and feed graphics to TCON. If M is | 40 | do some alpha blending and feed graphics to TCON. If M is |
41 | selected the module will be called sun4i-backend. | 41 | selected the module will be called sun4i-backend. |
42 | 42 | ||
43 | config DRM_SUN8I_DW_HDMI | ||
44 | tristate "Support for Allwinner version of DesignWare HDMI" | ||
45 | depends on DRM_SUN4I | ||
46 | select DRM_DW_HDMI | ||
47 | help | ||
48 | Choose this option if you have an Allwinner SoC with the | ||
49 | DesignWare HDMI controller with custom HDMI PHY. If M is | ||
50 | selected the module will be called sun8i_dw_hdmi. | ||
51 | |||
43 | config DRM_SUN8I_MIXER | 52 | config DRM_SUN8I_MIXER |
44 | tristate "Support for Allwinner Display Engine 2.0 Mixer" | 53 | tristate "Support for Allwinner Display Engine 2.0 Mixer" |
45 | default MACH_SUN8I | 54 | default MACH_SUN8I |
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 582607c0c488..1610e748119b 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile | |||
@@ -10,6 +10,9 @@ sun4i-drm-hdmi-y += sun4i_hdmi_enc.o | |||
10 | sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o | 10 | sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o |
11 | sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o | 11 | sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o |
12 | 12 | ||
13 | sun8i-drm-hdmi-y += sun8i_dw_hdmi.o | ||
14 | sun8i-drm-hdmi-y += sun8i_hdmi_phy.o | ||
15 | |||
13 | sun8i-mixer-y += sun8i_mixer.o sun8i_ui_layer.o \ | 16 | sun8i-mixer-y += sun8i_mixer.o sun8i_ui_layer.o \ |
14 | sun8i_vi_layer.o sun8i_ui_scaler.o \ | 17 | sun8i_vi_layer.o sun8i_ui_scaler.o \ |
15 | sun8i_vi_scaler.o sun8i_csc.o | 18 | sun8i_vi_scaler.o sun8i_csc.o |
@@ -27,4 +30,5 @@ obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o | |||
27 | 30 | ||
28 | obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o sun4i-frontend.o | 31 | obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o sun4i-frontend.o |
29 | obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o | 32 | obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o |
33 | obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i-drm-hdmi.o | ||
30 | obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o | 34 | obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o |
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c new file mode 100644 index 000000000000..9f40a44b456b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c | |||
@@ -0,0 +1,196 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0+ | ||
2 | /* | ||
3 | * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> | ||
4 | */ | ||
5 | |||
6 | #include <linux/component.h> | ||
7 | #include <linux/module.h> | ||
8 | #include <linux/platform_device.h> | ||
9 | |||
10 | #include <drm/drm_of.h> | ||
11 | #include <drm/drmP.h> | ||
12 | #include <drm/drm_crtc_helper.h> | ||
13 | |||
14 | #include "sun8i_dw_hdmi.h" | ||
15 | |||
16 | static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder, | ||
17 | struct drm_display_mode *mode, | ||
18 | struct drm_display_mode *adj_mode) | ||
19 | { | ||
20 | struct sun8i_dw_hdmi *hdmi = encoder_to_sun8i_dw_hdmi(encoder); | ||
21 | |||
22 | clk_set_rate(hdmi->clk_tmds, mode->crtc_clock * 1000); | ||
23 | } | ||
24 | |||
25 | static const struct drm_encoder_helper_funcs | ||
26 | sun8i_dw_hdmi_encoder_helper_funcs = { | ||
27 | .mode_set = sun8i_dw_hdmi_encoder_mode_set, | ||
28 | }; | ||
29 | |||
30 | static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = { | ||
31 | .destroy = drm_encoder_cleanup, | ||
32 | }; | ||
33 | |||
34 | static enum drm_mode_status | ||
35 | sun8i_dw_hdmi_mode_valid(struct drm_connector *connector, | ||
36 | const struct drm_display_mode *mode) | ||
37 | { | ||
38 | if (mode->clock > 297000) | ||
39 | return MODE_CLOCK_HIGH; | ||
40 | |||
41 | return MODE_OK; | ||
42 | } | ||
43 | |||
44 | static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master, | ||
45 | void *data) | ||
46 | { | ||
47 | struct platform_device *pdev = to_platform_device(dev); | ||
48 | struct dw_hdmi_plat_data *plat_data; | ||
49 | struct drm_device *drm = data; | ||
50 | struct device_node *phy_node; | ||
51 | struct drm_encoder *encoder; | ||
52 | struct sun8i_dw_hdmi *hdmi; | ||
53 | int ret; | ||
54 | |||
55 | if (!pdev->dev.of_node) | ||
56 | return -ENODEV; | ||
57 | |||
58 | hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); | ||
59 | if (!hdmi) | ||
60 | return -ENOMEM; | ||
61 | |||
62 | plat_data = &hdmi->plat_data; | ||
63 | hdmi->dev = &pdev->dev; | ||
64 | encoder = &hdmi->encoder; | ||
65 | |||
66 | encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); | ||
67 | /* | ||
68 | * If we failed to find the CRTC(s) which this encoder is | ||
69 | * supposed to be connected to, it's because the CRTC has | ||
70 | * not been registered yet. Defer probing, and hope that | ||
71 | * the required CRTC is added later. | ||
72 | */ | ||
73 | if (encoder->possible_crtcs == 0) | ||
74 | return -EPROBE_DEFER; | ||
75 | |||
76 | hdmi->rst_ctrl = devm_reset_control_get(dev, "ctrl"); | ||
77 | if (IS_ERR(hdmi->rst_ctrl)) { | ||
78 | dev_err(dev, "Could not get ctrl reset control\n"); | ||
79 | return PTR_ERR(hdmi->rst_ctrl); | ||
80 | } | ||
81 | |||
82 | hdmi->clk_tmds = devm_clk_get(dev, "tmds"); | ||
83 | if (IS_ERR(hdmi->clk_tmds)) { | ||
84 | dev_err(dev, "Couldn't get the tmds clock\n"); | ||
85 | return PTR_ERR(hdmi->clk_tmds); | ||
86 | } | ||
87 | |||
88 | ret = reset_control_deassert(hdmi->rst_ctrl); | ||
89 | if (ret) { | ||
90 | dev_err(dev, "Could not deassert ctrl reset control\n"); | ||
91 | return ret; | ||
92 | } | ||
93 | |||
94 | ret = clk_prepare_enable(hdmi->clk_tmds); | ||
95 | if (ret) { | ||
96 | dev_err(dev, "Could not enable tmds clock\n"); | ||
97 | goto err_assert_ctrl_reset; | ||
98 | } | ||
99 | |||
100 | phy_node = of_parse_phandle(dev->of_node, "phys", 0); | ||
101 | if (!phy_node) { | ||
102 | dev_err(dev, "Can't found PHY phandle\n"); | ||
103 | goto err_disable_clk_tmds; | ||
104 | } | ||
105 | |||
106 | ret = sun8i_hdmi_phy_probe(hdmi, phy_node); | ||
107 | of_node_put(phy_node); | ||
108 | if (ret) { | ||
109 | dev_err(dev, "Couldn't get the HDMI PHY\n"); | ||
110 | goto err_disable_clk_tmds; | ||
111 | } | ||
112 | |||
113 | drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs); | ||
114 | drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs, | ||
115 | DRM_MODE_ENCODER_TMDS, NULL); | ||
116 | |||
117 | sun8i_hdmi_phy_init(hdmi->phy); | ||
118 | |||
119 | plat_data->mode_valid = &sun8i_dw_hdmi_mode_valid; | ||
120 | plat_data->phy_ops = sun8i_hdmi_phy_get_ops(); | ||
121 | plat_data->phy_name = "sun8i_dw_hdmi_phy"; | ||
122 | plat_data->phy_data = hdmi->phy; | ||
123 | |||
124 | platform_set_drvdata(pdev, hdmi); | ||
125 | |||
126 | hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data); | ||
127 | |||
128 | /* | ||
129 | * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), | ||
130 | * which would have called the encoder cleanup. Do it manually. | ||
131 | */ | ||
132 | if (IS_ERR(hdmi->hdmi)) { | ||
133 | ret = PTR_ERR(hdmi->hdmi); | ||
134 | goto cleanup_encoder; | ||
135 | } | ||
136 | |||
137 | return 0; | ||
138 | |||
139 | cleanup_encoder: | ||
140 | drm_encoder_cleanup(encoder); | ||
141 | sun8i_hdmi_phy_remove(hdmi); | ||
142 | err_disable_clk_tmds: | ||
143 | clk_disable_unprepare(hdmi->clk_tmds); | ||
144 | err_assert_ctrl_reset: | ||
145 | reset_control_assert(hdmi->rst_ctrl); | ||
146 | |||
147 | return ret; | ||
148 | } | ||
149 | |||
150 | static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master, | ||
151 | void *data) | ||
152 | { | ||
153 | struct sun8i_dw_hdmi *hdmi = dev_get_drvdata(dev); | ||
154 | |||
155 | dw_hdmi_unbind(hdmi->hdmi); | ||
156 | sun8i_hdmi_phy_remove(hdmi); | ||
157 | clk_disable_unprepare(hdmi->clk_tmds); | ||
158 | reset_control_assert(hdmi->rst_ctrl); | ||
159 | } | ||
160 | |||
161 | static const struct component_ops sun8i_dw_hdmi_ops = { | ||
162 | .bind = sun8i_dw_hdmi_bind, | ||
163 | .unbind = sun8i_dw_hdmi_unbind, | ||
164 | }; | ||
165 | |||
166 | static int sun8i_dw_hdmi_probe(struct platform_device *pdev) | ||
167 | { | ||
168 | return component_add(&pdev->dev, &sun8i_dw_hdmi_ops); | ||
169 | } | ||
170 | |||
171 | static int sun8i_dw_hdmi_remove(struct platform_device *pdev) | ||
172 | { | ||
173 | component_del(&pdev->dev, &sun8i_dw_hdmi_ops); | ||
174 | |||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = { | ||
179 | { .compatible = "allwinner,sun8i-a83t-dw-hdmi" }, | ||
180 | { /* sentinel */ }, | ||
181 | }; | ||
182 | MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids); | ||
183 | |||
184 | struct platform_driver sun8i_dw_hdmi_pltfm_driver = { | ||
185 | .probe = sun8i_dw_hdmi_probe, | ||
186 | .remove = sun8i_dw_hdmi_remove, | ||
187 | .driver = { | ||
188 | .name = "sun8i-dw-hdmi", | ||
189 | .of_match_table = sun8i_dw_hdmi_dt_ids, | ||
190 | }, | ||
191 | }; | ||
192 | module_platform_driver(sun8i_dw_hdmi_pltfm_driver); | ||
193 | |||
194 | MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>"); | ||
195 | MODULE_DESCRIPTION("Allwinner DW HDMI bridge"); | ||
196 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h new file mode 100644 index 000000000000..d8d0684fc8aa --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h | |||
@@ -0,0 +1,44 @@ | |||
1 | /* SPDX-License-Identifier: GPL-2.0+ */ | ||
2 | /* | ||
3 | * Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@siol.net> | ||
4 | */ | ||
5 | |||
6 | #ifndef _SUN8I_DW_HDMI_H_ | ||
7 | #define _SUN8I_DW_HDMI_H_ | ||
8 | |||
9 | #include <drm/bridge/dw_hdmi.h> | ||
10 | #include <drm/drm_encoder.h> | ||
11 | #include <linux/clk.h> | ||
12 | #include <linux/regmap.h> | ||
13 | #include <linux/reset.h> | ||
14 | |||
15 | struct sun8i_hdmi_phy { | ||
16 | struct clk *clk_bus; | ||
17 | struct clk *clk_mod; | ||
18 | struct regmap *regs; | ||
19 | struct reset_control *rst_phy; | ||
20 | }; | ||
21 | |||
22 | struct sun8i_dw_hdmi { | ||
23 | struct clk *clk_tmds; | ||
24 | struct device *dev; | ||
25 | struct dw_hdmi *hdmi; | ||
26 | struct drm_encoder encoder; | ||
27 | struct sun8i_hdmi_phy *phy; | ||
28 | struct dw_hdmi_plat_data plat_data; | ||
29 | struct reset_control *rst_ctrl; | ||
30 | }; | ||
31 | |||
32 | static inline struct sun8i_dw_hdmi * | ||
33 | encoder_to_sun8i_dw_hdmi(struct drm_encoder *encoder) | ||
34 | { | ||
35 | return container_of(encoder, struct sun8i_dw_hdmi, encoder); | ||
36 | } | ||
37 | |||
38 | int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node); | ||
39 | void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi); | ||
40 | |||
41 | void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy); | ||
42 | const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void); | ||
43 | |||
44 | #endif /* _SUN8I_DW_HDMI_H_ */ | ||
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c new file mode 100644 index 000000000000..e5bfcdd43ec9 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | |||
@@ -0,0 +1,270 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0+ | ||
2 | /* | ||
3 | * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> | ||
4 | */ | ||
5 | |||
6 | #include <linux/of_address.h> | ||
7 | |||
8 | #include "sun8i_dw_hdmi.h" | ||
9 | |||
10 | #define SUN8I_HDMI_PHY_DBG_CTRL_REG 0x0000 | ||
11 | #define SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK BIT(0) | ||
12 | #define SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK GENMASK(15, 8) | ||
13 | #define SUN8I_HDMI_PHY_DBG_CTRL_POL(val) (val << 8) | ||
14 | #define SUN8I_HDMI_PHY_DBG_CTRL_ADDR_MASK GENMASK(23, 16) | ||
15 | #define SUN8I_HDMI_PHY_DBG_CTRL_ADDR(addr) (addr << 16) | ||
16 | |||
17 | #define SUN8I_HDMI_PHY_REXT_CTRL_REG 0x0004 | ||
18 | #define SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN BIT(31) | ||
19 | |||
20 | #define SUN8I_HDMI_PHY_READ_EN_REG 0x0010 | ||
21 | #define SUN8I_HDMI_PHY_READ_EN_MAGIC 0x54524545 | ||
22 | |||
23 | #define SUN8I_HDMI_PHY_UNSCRAMBLE_REG 0x0014 | ||
24 | #define SUN8I_HDMI_PHY_UNSCRAMBLE_MAGIC 0x42494E47 | ||
25 | |||
26 | /* | ||
27 | * Address can be actually any value. Here is set to same value as | ||
28 | * it is set in BSP driver. | ||
29 | */ | ||
30 | #define I2C_ADDR 0x69 | ||
31 | |||
32 | static int sun8i_hdmi_phy_config(struct dw_hdmi *hdmi, void *data, | ||
33 | struct drm_display_mode *mode) | ||
34 | { | ||
35 | struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data; | ||
36 | u32 val = 0; | ||
37 | |||
38 | if ((mode->flags & DRM_MODE_FLAG_NHSYNC) && | ||
39 | (mode->flags & DRM_MODE_FLAG_NHSYNC)) { | ||
40 | val = 0x03; | ||
41 | } | ||
42 | |||
43 | regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, | ||
44 | SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, | ||
45 | SUN8I_HDMI_PHY_DBG_CTRL_POL(val)); | ||
46 | |||
47 | regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG, | ||
48 | SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, | ||
49 | SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN); | ||
50 | |||
51 | /* power down */ | ||
52 | dw_hdmi_phy_gen2_txpwron(hdmi, 0); | ||
53 | dw_hdmi_phy_gen2_pddq(hdmi, 1); | ||
54 | |||
55 | dw_hdmi_phy_reset(hdmi); | ||
56 | |||
57 | dw_hdmi_phy_gen2_pddq(hdmi, 0); | ||
58 | |||
59 | dw_hdmi_phy_i2c_set_addr(hdmi, I2C_ADDR); | ||
60 | |||
61 | /* | ||
62 | * Values are taken from BSP HDMI driver. Although AW didn't | ||
63 | * release any documentation, explanation of this values can | ||
64 | * be found in i.MX 6Dual/6Quad Reference Manual. | ||
65 | */ | ||
66 | if (mode->crtc_clock <= 27000) { | ||
67 | dw_hdmi_phy_i2c_write(hdmi, 0x01e0, 0x06); | ||
68 | dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x15); | ||
69 | dw_hdmi_phy_i2c_write(hdmi, 0x08da, 0x10); | ||
70 | dw_hdmi_phy_i2c_write(hdmi, 0x0007, 0x19); | ||
71 | dw_hdmi_phy_i2c_write(hdmi, 0x0318, 0x0e); | ||
72 | dw_hdmi_phy_i2c_write(hdmi, 0x8009, 0x09); | ||
73 | } else if (mode->crtc_clock <= 74250) { | ||
74 | dw_hdmi_phy_i2c_write(hdmi, 0x0540, 0x06); | ||
75 | dw_hdmi_phy_i2c_write(hdmi, 0x0005, 0x15); | ||
76 | dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10); | ||
77 | dw_hdmi_phy_i2c_write(hdmi, 0x0007, 0x19); | ||
78 | dw_hdmi_phy_i2c_write(hdmi, 0x02b5, 0x0e); | ||
79 | dw_hdmi_phy_i2c_write(hdmi, 0x8009, 0x09); | ||
80 | } else if (mode->crtc_clock <= 148500) { | ||
81 | dw_hdmi_phy_i2c_write(hdmi, 0x04a0, 0x06); | ||
82 | dw_hdmi_phy_i2c_write(hdmi, 0x000a, 0x15); | ||
83 | dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10); | ||
84 | dw_hdmi_phy_i2c_write(hdmi, 0x0002, 0x19); | ||
85 | dw_hdmi_phy_i2c_write(hdmi, 0x0021, 0x0e); | ||
86 | dw_hdmi_phy_i2c_write(hdmi, 0x8029, 0x09); | ||
87 | } else { | ||
88 | dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x06); | ||
89 | dw_hdmi_phy_i2c_write(hdmi, 0x000f, 0x15); | ||
90 | dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10); | ||
91 | dw_hdmi_phy_i2c_write(hdmi, 0x0002, 0x19); | ||
92 | dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x0e); | ||
93 | dw_hdmi_phy_i2c_write(hdmi, 0x802b, 0x09); | ||
94 | } | ||
95 | |||
96 | dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x1e); | ||
97 | dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); | ||
98 | dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x17); | ||
99 | |||
100 | dw_hdmi_phy_gen2_txpwron(hdmi, 1); | ||
101 | |||
102 | return 0; | ||
103 | }; | ||
104 | |||
105 | static void sun8i_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data) | ||
106 | { | ||
107 | struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data; | ||
108 | |||
109 | dw_hdmi_phy_gen2_txpwron(hdmi, 0); | ||
110 | dw_hdmi_phy_gen2_pddq(hdmi, 1); | ||
111 | |||
112 | regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG, | ||
113 | SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, 0); | ||
114 | } | ||
115 | |||
116 | static const struct dw_hdmi_phy_ops sun8i_hdmi_phy_ops = { | ||
117 | .init = &sun8i_hdmi_phy_config, | ||
118 | .disable = &sun8i_hdmi_phy_disable, | ||
119 | .read_hpd = &dw_hdmi_phy_read_hpd, | ||
120 | .update_hpd = &dw_hdmi_phy_update_hpd, | ||
121 | .setup_hpd = &dw_hdmi_phy_setup_hpd, | ||
122 | }; | ||
123 | |||
124 | void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy) | ||
125 | { | ||
126 | /* enable read access to HDMI controller */ | ||
127 | regmap_write(phy->regs, SUN8I_HDMI_PHY_READ_EN_REG, | ||
128 | SUN8I_HDMI_PHY_READ_EN_MAGIC); | ||
129 | |||
130 | /* unscramble register offsets */ | ||
131 | regmap_write(phy->regs, SUN8I_HDMI_PHY_UNSCRAMBLE_REG, | ||
132 | SUN8I_HDMI_PHY_UNSCRAMBLE_MAGIC); | ||
133 | |||
134 | regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, | ||
135 | SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK, | ||
136 | SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK); | ||
137 | |||
138 | /* | ||
139 | * Set PHY I2C address. It must match to the address set by | ||
140 | * dw_hdmi_phy_set_slave_addr(). | ||
141 | */ | ||
142 | regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, | ||
143 | SUN8I_HDMI_PHY_DBG_CTRL_ADDR_MASK, | ||
144 | SUN8I_HDMI_PHY_DBG_CTRL_ADDR(I2C_ADDR)); | ||
145 | } | ||
146 | |||
147 | const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void) | ||
148 | { | ||
149 | return &sun8i_hdmi_phy_ops; | ||
150 | } | ||
151 | |||
152 | static struct regmap_config sun8i_hdmi_phy_regmap_config = { | ||
153 | .reg_bits = 32, | ||
154 | .val_bits = 32, | ||
155 | .reg_stride = 4, | ||
156 | .max_register = SUN8I_HDMI_PHY_UNSCRAMBLE_REG, | ||
157 | .name = "phy" | ||
158 | }; | ||
159 | |||
160 | static const struct of_device_id sun8i_hdmi_phy_of_table[] = { | ||
161 | { .compatible = "allwinner,sun8i-a83t-hdmi-phy" }, | ||
162 | { /* sentinel */ } | ||
163 | }; | ||
164 | |||
165 | int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node) | ||
166 | { | ||
167 | struct device *dev = hdmi->dev; | ||
168 | struct sun8i_hdmi_phy *phy; | ||
169 | struct resource res; | ||
170 | void __iomem *regs; | ||
171 | int ret; | ||
172 | |||
173 | if (!of_match_node(sun8i_hdmi_phy_of_table, node)) { | ||
174 | dev_err(dev, "Incompatible HDMI PHY\n"); | ||
175 | return -EINVAL; | ||
176 | } | ||
177 | |||
178 | phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); | ||
179 | if (!phy) | ||
180 | return -ENOMEM; | ||
181 | |||
182 | ret = of_address_to_resource(node, 0, &res); | ||
183 | if (ret) { | ||
184 | dev_err(dev, "phy: Couldn't get our resources\n"); | ||
185 | return ret; | ||
186 | } | ||
187 | |||
188 | regs = devm_ioremap_resource(dev, &res); | ||
189 | if (IS_ERR(regs)) { | ||
190 | dev_err(dev, "Couldn't map the HDMI PHY registers\n"); | ||
191 | return PTR_ERR(regs); | ||
192 | } | ||
193 | |||
194 | phy->regs = devm_regmap_init_mmio(dev, regs, | ||
195 | &sun8i_hdmi_phy_regmap_config); | ||
196 | if (IS_ERR(phy->regs)) { | ||
197 | dev_err(dev, "Couldn't create the HDMI PHY regmap\n"); | ||
198 | return PTR_ERR(phy->regs); | ||
199 | } | ||
200 | |||
201 | phy->clk_bus = of_clk_get_by_name(node, "bus"); | ||
202 | if (IS_ERR(phy->clk_bus)) { | ||
203 | dev_err(dev, "Could not get bus clock\n"); | ||
204 | return PTR_ERR(phy->clk_bus); | ||
205 | } | ||
206 | |||
207 | phy->clk_mod = of_clk_get_by_name(node, "mod"); | ||
208 | if (IS_ERR(phy->clk_mod)) { | ||
209 | dev_err(dev, "Could not get mod clock\n"); | ||
210 | ret = PTR_ERR(phy->clk_mod); | ||
211 | goto err_put_clk_bus; | ||
212 | } | ||
213 | |||
214 | phy->rst_phy = of_reset_control_get_shared(node, "phy"); | ||
215 | if (IS_ERR(phy->rst_phy)) { | ||
216 | dev_err(dev, "Could not get phy reset control\n"); | ||
217 | ret = PTR_ERR(phy->rst_phy); | ||
218 | goto err_put_clk_mod; | ||
219 | } | ||
220 | |||
221 | ret = reset_control_deassert(phy->rst_phy); | ||
222 | if (ret) { | ||
223 | dev_err(dev, "Cannot deassert phy reset control: %d\n", ret); | ||
224 | goto err_put_rst_phy; | ||
225 | } | ||
226 | |||
227 | ret = clk_prepare_enable(phy->clk_bus); | ||
228 | if (ret) { | ||
229 | dev_err(dev, "Cannot enable bus clock: %d\n", ret); | ||
230 | goto err_deassert_rst_phy; | ||
231 | } | ||
232 | |||
233 | ret = clk_prepare_enable(phy->clk_mod); | ||
234 | if (ret) { | ||
235 | dev_err(dev, "Cannot enable mod clock: %d\n", ret); | ||
236 | goto err_disable_clk_bus; | ||
237 | } | ||
238 | |||
239 | hdmi->phy = phy; | ||
240 | |||
241 | return 0; | ||
242 | |||
243 | err_disable_clk_bus: | ||
244 | clk_disable_unprepare(phy->clk_bus); | ||
245 | err_deassert_rst_phy: | ||
246 | reset_control_assert(phy->rst_phy); | ||
247 | err_put_rst_phy: | ||
248 | reset_control_put(phy->rst_phy); | ||
249 | err_put_clk_mod: | ||
250 | clk_put(phy->clk_mod); | ||
251 | err_put_clk_bus: | ||
252 | clk_put(phy->clk_bus); | ||
253 | |||
254 | return ret; | ||
255 | } | ||
256 | |||
257 | void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi) | ||
258 | { | ||
259 | struct sun8i_hdmi_phy *phy = hdmi->phy; | ||
260 | |||
261 | clk_disable_unprepare(phy->clk_mod); | ||
262 | clk_disable_unprepare(phy->clk_bus); | ||
263 | |||
264 | reset_control_assert(phy->rst_phy); | ||
265 | |||
266 | reset_control_put(phy->rst_phy); | ||
267 | |||
268 | clk_put(phy->clk_mod); | ||
269 | clk_put(phy->clk_bus); | ||
270 | } | ||