1*4b6cb2b6SLucas Stach // SPDX-License-Identifier: GPL-2.0
2*4b6cb2b6SLucas Stach /* Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
3*4b6cb2b6SLucas Stach *
4*4b6cb2b6SLucas Stach * derived from imx-hdmi.c(renamed to bridge/dw_hdmi.c now)
5*4b6cb2b6SLucas Stach */
6*4b6cb2b6SLucas Stach
7*4b6cb2b6SLucas Stach #include <linux/component.h>
8*4b6cb2b6SLucas Stach #include <linux/mfd/syscon.h>
9*4b6cb2b6SLucas Stach #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
10*4b6cb2b6SLucas Stach #include <linux/module.h>
11*4b6cb2b6SLucas Stach #include <linux/platform_device.h>
12*4b6cb2b6SLucas Stach #include <linux/regmap.h>
13*4b6cb2b6SLucas Stach
14*4b6cb2b6SLucas Stach #include <video/imx-ipu-v3.h>
15*4b6cb2b6SLucas Stach
16*4b6cb2b6SLucas Stach #include <drm/bridge/dw_hdmi.h>
17*4b6cb2b6SLucas Stach #include <drm/drm_atomic_helper.h>
18*4b6cb2b6SLucas Stach #include <drm/drm_bridge.h>
19*4b6cb2b6SLucas Stach #include <drm/drm_edid.h>
20*4b6cb2b6SLucas Stach #include <drm/drm_encoder.h>
21*4b6cb2b6SLucas Stach #include <drm/drm_managed.h>
22*4b6cb2b6SLucas Stach #include <drm/drm_of.h>
23*4b6cb2b6SLucas Stach #include <drm/drm_simple_kms_helper.h>
24*4b6cb2b6SLucas Stach
25*4b6cb2b6SLucas Stach #include "imx-drm.h"
26*4b6cb2b6SLucas Stach
27*4b6cb2b6SLucas Stach struct imx_hdmi;
28*4b6cb2b6SLucas Stach
29*4b6cb2b6SLucas Stach struct imx_hdmi_encoder {
30*4b6cb2b6SLucas Stach struct drm_encoder encoder;
31*4b6cb2b6SLucas Stach struct imx_hdmi *hdmi;
32*4b6cb2b6SLucas Stach };
33*4b6cb2b6SLucas Stach
34*4b6cb2b6SLucas Stach struct imx_hdmi {
35*4b6cb2b6SLucas Stach struct device *dev;
36*4b6cb2b6SLucas Stach struct drm_bridge *bridge;
37*4b6cb2b6SLucas Stach struct dw_hdmi *hdmi;
38*4b6cb2b6SLucas Stach struct regmap *regmap;
39*4b6cb2b6SLucas Stach };
40*4b6cb2b6SLucas Stach
enc_to_imx_hdmi(struct drm_encoder * e)41*4b6cb2b6SLucas Stach static inline struct imx_hdmi *enc_to_imx_hdmi(struct drm_encoder *e)
42*4b6cb2b6SLucas Stach {
43*4b6cb2b6SLucas Stach return container_of(e, struct imx_hdmi_encoder, encoder)->hdmi;
44*4b6cb2b6SLucas Stach }
45*4b6cb2b6SLucas Stach
46*4b6cb2b6SLucas Stach static const struct dw_hdmi_mpll_config imx_mpll_cfg[] = {
47*4b6cb2b6SLucas Stach {
48*4b6cb2b6SLucas Stach 45250000, {
49*4b6cb2b6SLucas Stach { 0x01e0, 0x0000 },
50*4b6cb2b6SLucas Stach { 0x21e1, 0x0000 },
51*4b6cb2b6SLucas Stach { 0x41e2, 0x0000 }
52*4b6cb2b6SLucas Stach },
53*4b6cb2b6SLucas Stach }, {
54*4b6cb2b6SLucas Stach 92500000, {
55*4b6cb2b6SLucas Stach { 0x0140, 0x0005 },
56*4b6cb2b6SLucas Stach { 0x2141, 0x0005 },
57*4b6cb2b6SLucas Stach { 0x4142, 0x0005 },
58*4b6cb2b6SLucas Stach },
59*4b6cb2b6SLucas Stach }, {
60*4b6cb2b6SLucas Stach 148500000, {
61*4b6cb2b6SLucas Stach { 0x00a0, 0x000a },
62*4b6cb2b6SLucas Stach { 0x20a1, 0x000a },
63*4b6cb2b6SLucas Stach { 0x40a2, 0x000a },
64*4b6cb2b6SLucas Stach },
65*4b6cb2b6SLucas Stach }, {
66*4b6cb2b6SLucas Stach 216000000, {
67*4b6cb2b6SLucas Stach { 0x00a0, 0x000a },
68*4b6cb2b6SLucas Stach { 0x2001, 0x000f },
69*4b6cb2b6SLucas Stach { 0x4002, 0x000f },
70*4b6cb2b6SLucas Stach },
71*4b6cb2b6SLucas Stach }, {
72*4b6cb2b6SLucas Stach ~0UL, {
73*4b6cb2b6SLucas Stach { 0x0000, 0x0000 },
74*4b6cb2b6SLucas Stach { 0x0000, 0x0000 },
75*4b6cb2b6SLucas Stach { 0x0000, 0x0000 },
76*4b6cb2b6SLucas Stach },
77*4b6cb2b6SLucas Stach }
78*4b6cb2b6SLucas Stach };
79*4b6cb2b6SLucas Stach
80*4b6cb2b6SLucas Stach static const struct dw_hdmi_curr_ctrl imx_cur_ctr[] = {
81*4b6cb2b6SLucas Stach /* pixelclk bpp8 bpp10 bpp12 */
82*4b6cb2b6SLucas Stach {
83*4b6cb2b6SLucas Stach 54000000, { 0x091c, 0x091c, 0x06dc },
84*4b6cb2b6SLucas Stach }, {
85*4b6cb2b6SLucas Stach 58400000, { 0x091c, 0x06dc, 0x06dc },
86*4b6cb2b6SLucas Stach }, {
87*4b6cb2b6SLucas Stach 72000000, { 0x06dc, 0x06dc, 0x091c },
88*4b6cb2b6SLucas Stach }, {
89*4b6cb2b6SLucas Stach 74250000, { 0x06dc, 0x0b5c, 0x091c },
90*4b6cb2b6SLucas Stach }, {
91*4b6cb2b6SLucas Stach 118800000, { 0x091c, 0x091c, 0x06dc },
92*4b6cb2b6SLucas Stach }, {
93*4b6cb2b6SLucas Stach 216000000, { 0x06dc, 0x0b5c, 0x091c },
94*4b6cb2b6SLucas Stach }, {
95*4b6cb2b6SLucas Stach ~0UL, { 0x0000, 0x0000, 0x0000 },
96*4b6cb2b6SLucas Stach },
97*4b6cb2b6SLucas Stach };
98*4b6cb2b6SLucas Stach
99*4b6cb2b6SLucas Stach /*
100*4b6cb2b6SLucas Stach * Resistance term 133Ohm Cfg
101*4b6cb2b6SLucas Stach * PREEMP config 0.00
102*4b6cb2b6SLucas Stach * TX/CK level 10
103*4b6cb2b6SLucas Stach */
104*4b6cb2b6SLucas Stach static const struct dw_hdmi_phy_config imx_phy_config[] = {
105*4b6cb2b6SLucas Stach /*pixelclk symbol term vlev */
106*4b6cb2b6SLucas Stach { 216000000, 0x800d, 0x0005, 0x01ad},
107*4b6cb2b6SLucas Stach { ~0UL, 0x0000, 0x0000, 0x0000}
108*4b6cb2b6SLucas Stach };
109*4b6cb2b6SLucas Stach
dw_hdmi_imx_encoder_enable(struct drm_encoder * encoder)110*4b6cb2b6SLucas Stach static void dw_hdmi_imx_encoder_enable(struct drm_encoder *encoder)
111*4b6cb2b6SLucas Stach {
112*4b6cb2b6SLucas Stach struct imx_hdmi *hdmi = enc_to_imx_hdmi(encoder);
113*4b6cb2b6SLucas Stach int mux = drm_of_encoder_active_port_id(hdmi->dev->of_node, encoder);
114*4b6cb2b6SLucas Stach
115*4b6cb2b6SLucas Stach regmap_update_bits(hdmi->regmap, IOMUXC_GPR3,
116*4b6cb2b6SLucas Stach IMX6Q_GPR3_HDMI_MUX_CTL_MASK,
117*4b6cb2b6SLucas Stach mux << IMX6Q_GPR3_HDMI_MUX_CTL_SHIFT);
118*4b6cb2b6SLucas Stach }
119*4b6cb2b6SLucas Stach
dw_hdmi_imx_atomic_check(struct drm_encoder * encoder,struct drm_crtc_state * crtc_state,struct drm_connector_state * conn_state)120*4b6cb2b6SLucas Stach static int dw_hdmi_imx_atomic_check(struct drm_encoder *encoder,
121*4b6cb2b6SLucas Stach struct drm_crtc_state *crtc_state,
122*4b6cb2b6SLucas Stach struct drm_connector_state *conn_state)
123*4b6cb2b6SLucas Stach {
124*4b6cb2b6SLucas Stach struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
125*4b6cb2b6SLucas Stach
126*4b6cb2b6SLucas Stach imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
127*4b6cb2b6SLucas Stach imx_crtc_state->di_hsync_pin = 2;
128*4b6cb2b6SLucas Stach imx_crtc_state->di_vsync_pin = 3;
129*4b6cb2b6SLucas Stach
130*4b6cb2b6SLucas Stach return 0;
131*4b6cb2b6SLucas Stach }
132*4b6cb2b6SLucas Stach
133*4b6cb2b6SLucas Stach static const struct drm_encoder_helper_funcs dw_hdmi_imx_encoder_helper_funcs = {
134*4b6cb2b6SLucas Stach .enable = dw_hdmi_imx_encoder_enable,
135*4b6cb2b6SLucas Stach .atomic_check = dw_hdmi_imx_atomic_check,
136*4b6cb2b6SLucas Stach };
137*4b6cb2b6SLucas Stach
138*4b6cb2b6SLucas Stach static enum drm_mode_status
imx6q_hdmi_mode_valid(struct dw_hdmi * hdmi,void * data,const struct drm_display_info * info,const struct drm_display_mode * mode)139*4b6cb2b6SLucas Stach imx6q_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
140*4b6cb2b6SLucas Stach const struct drm_display_info *info,
141*4b6cb2b6SLucas Stach const struct drm_display_mode *mode)
142*4b6cb2b6SLucas Stach {
143*4b6cb2b6SLucas Stach if (mode->clock < 13500)
144*4b6cb2b6SLucas Stach return MODE_CLOCK_LOW;
145*4b6cb2b6SLucas Stach /* FIXME: Hardware is capable of 266MHz, but setup data is missing. */
146*4b6cb2b6SLucas Stach if (mode->clock > 216000)
147*4b6cb2b6SLucas Stach return MODE_CLOCK_HIGH;
148*4b6cb2b6SLucas Stach
149*4b6cb2b6SLucas Stach return MODE_OK;
150*4b6cb2b6SLucas Stach }
151*4b6cb2b6SLucas Stach
152*4b6cb2b6SLucas Stach static enum drm_mode_status
imx6dl_hdmi_mode_valid(struct dw_hdmi * hdmi,void * data,const struct drm_display_info * info,const struct drm_display_mode * mode)153*4b6cb2b6SLucas Stach imx6dl_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
154*4b6cb2b6SLucas Stach const struct drm_display_info *info,
155*4b6cb2b6SLucas Stach const struct drm_display_mode *mode)
156*4b6cb2b6SLucas Stach {
157*4b6cb2b6SLucas Stach if (mode->clock < 13500)
158*4b6cb2b6SLucas Stach return MODE_CLOCK_LOW;
159*4b6cb2b6SLucas Stach /* FIXME: Hardware is capable of 270MHz, but setup data is missing. */
160*4b6cb2b6SLucas Stach if (mode->clock > 216000)
161*4b6cb2b6SLucas Stach return MODE_CLOCK_HIGH;
162*4b6cb2b6SLucas Stach
163*4b6cb2b6SLucas Stach return MODE_OK;
164*4b6cb2b6SLucas Stach }
165*4b6cb2b6SLucas Stach
166*4b6cb2b6SLucas Stach static struct dw_hdmi_plat_data imx6q_hdmi_drv_data = {
167*4b6cb2b6SLucas Stach .mpll_cfg = imx_mpll_cfg,
168*4b6cb2b6SLucas Stach .cur_ctr = imx_cur_ctr,
169*4b6cb2b6SLucas Stach .phy_config = imx_phy_config,
170*4b6cb2b6SLucas Stach .mode_valid = imx6q_hdmi_mode_valid,
171*4b6cb2b6SLucas Stach };
172*4b6cb2b6SLucas Stach
173*4b6cb2b6SLucas Stach static struct dw_hdmi_plat_data imx6dl_hdmi_drv_data = {
174*4b6cb2b6SLucas Stach .mpll_cfg = imx_mpll_cfg,
175*4b6cb2b6SLucas Stach .cur_ctr = imx_cur_ctr,
176*4b6cb2b6SLucas Stach .phy_config = imx_phy_config,
177*4b6cb2b6SLucas Stach .mode_valid = imx6dl_hdmi_mode_valid,
178*4b6cb2b6SLucas Stach };
179*4b6cb2b6SLucas Stach
180*4b6cb2b6SLucas Stach static const struct of_device_id dw_hdmi_imx_dt_ids[] = {
181*4b6cb2b6SLucas Stach { .compatible = "fsl,imx6q-hdmi",
182*4b6cb2b6SLucas Stach .data = &imx6q_hdmi_drv_data
183*4b6cb2b6SLucas Stach }, {
184*4b6cb2b6SLucas Stach .compatible = "fsl,imx6dl-hdmi",
185*4b6cb2b6SLucas Stach .data = &imx6dl_hdmi_drv_data
186*4b6cb2b6SLucas Stach },
187*4b6cb2b6SLucas Stach {},
188*4b6cb2b6SLucas Stach };
189*4b6cb2b6SLucas Stach MODULE_DEVICE_TABLE(of, dw_hdmi_imx_dt_ids);
190*4b6cb2b6SLucas Stach
dw_hdmi_imx_bind(struct device * dev,struct device * master,void * data)191*4b6cb2b6SLucas Stach static int dw_hdmi_imx_bind(struct device *dev, struct device *master,
192*4b6cb2b6SLucas Stach void *data)
193*4b6cb2b6SLucas Stach {
194*4b6cb2b6SLucas Stach struct drm_device *drm = data;
195*4b6cb2b6SLucas Stach struct imx_hdmi_encoder *hdmi_encoder;
196*4b6cb2b6SLucas Stach struct drm_encoder *encoder;
197*4b6cb2b6SLucas Stach int ret;
198*4b6cb2b6SLucas Stach
199*4b6cb2b6SLucas Stach hdmi_encoder = drmm_simple_encoder_alloc(drm, struct imx_hdmi_encoder,
200*4b6cb2b6SLucas Stach encoder, DRM_MODE_ENCODER_TMDS);
201*4b6cb2b6SLucas Stach if (IS_ERR(hdmi_encoder))
202*4b6cb2b6SLucas Stach return PTR_ERR(hdmi_encoder);
203*4b6cb2b6SLucas Stach
204*4b6cb2b6SLucas Stach hdmi_encoder->hdmi = dev_get_drvdata(dev);
205*4b6cb2b6SLucas Stach encoder = &hdmi_encoder->encoder;
206*4b6cb2b6SLucas Stach
207*4b6cb2b6SLucas Stach ret = imx_drm_encoder_parse_of(drm, encoder, dev->of_node);
208*4b6cb2b6SLucas Stach if (ret)
209*4b6cb2b6SLucas Stach return ret;
210*4b6cb2b6SLucas Stach
211*4b6cb2b6SLucas Stach drm_encoder_helper_add(encoder, &dw_hdmi_imx_encoder_helper_funcs);
212*4b6cb2b6SLucas Stach
213*4b6cb2b6SLucas Stach return drm_bridge_attach(encoder, hdmi_encoder->hdmi->bridge, NULL, 0);
214*4b6cb2b6SLucas Stach }
215*4b6cb2b6SLucas Stach
216*4b6cb2b6SLucas Stach static const struct component_ops dw_hdmi_imx_ops = {
217*4b6cb2b6SLucas Stach .bind = dw_hdmi_imx_bind,
218*4b6cb2b6SLucas Stach };
219*4b6cb2b6SLucas Stach
dw_hdmi_imx_probe(struct platform_device * pdev)220*4b6cb2b6SLucas Stach static int dw_hdmi_imx_probe(struct platform_device *pdev)
221*4b6cb2b6SLucas Stach {
222*4b6cb2b6SLucas Stach struct device_node *np = pdev->dev.of_node;
223*4b6cb2b6SLucas Stach const struct of_device_id *match = of_match_node(dw_hdmi_imx_dt_ids, np);
224*4b6cb2b6SLucas Stach struct imx_hdmi *hdmi;
225*4b6cb2b6SLucas Stach int ret;
226*4b6cb2b6SLucas Stach
227*4b6cb2b6SLucas Stach hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
228*4b6cb2b6SLucas Stach if (!hdmi)
229*4b6cb2b6SLucas Stach return -ENOMEM;
230*4b6cb2b6SLucas Stach
231*4b6cb2b6SLucas Stach platform_set_drvdata(pdev, hdmi);
232*4b6cb2b6SLucas Stach hdmi->dev = &pdev->dev;
233*4b6cb2b6SLucas Stach
234*4b6cb2b6SLucas Stach hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
235*4b6cb2b6SLucas Stach if (IS_ERR(hdmi->regmap)) {
236*4b6cb2b6SLucas Stach dev_err(hdmi->dev, "Unable to get gpr\n");
237*4b6cb2b6SLucas Stach return PTR_ERR(hdmi->regmap);
238*4b6cb2b6SLucas Stach }
239*4b6cb2b6SLucas Stach
240*4b6cb2b6SLucas Stach hdmi->hdmi = dw_hdmi_probe(pdev, match->data);
241*4b6cb2b6SLucas Stach if (IS_ERR(hdmi->hdmi))
242*4b6cb2b6SLucas Stach return PTR_ERR(hdmi->hdmi);
243*4b6cb2b6SLucas Stach
244*4b6cb2b6SLucas Stach hdmi->bridge = of_drm_find_bridge(np);
245*4b6cb2b6SLucas Stach if (!hdmi->bridge) {
246*4b6cb2b6SLucas Stach dev_err(hdmi->dev, "Unable to find bridge\n");
247*4b6cb2b6SLucas Stach dw_hdmi_remove(hdmi->hdmi);
248*4b6cb2b6SLucas Stach return -ENODEV;
249*4b6cb2b6SLucas Stach }
250*4b6cb2b6SLucas Stach
251*4b6cb2b6SLucas Stach ret = component_add(&pdev->dev, &dw_hdmi_imx_ops);
252*4b6cb2b6SLucas Stach if (ret)
253*4b6cb2b6SLucas Stach dw_hdmi_remove(hdmi->hdmi);
254*4b6cb2b6SLucas Stach
255*4b6cb2b6SLucas Stach return ret;
256*4b6cb2b6SLucas Stach }
257*4b6cb2b6SLucas Stach
dw_hdmi_imx_remove(struct platform_device * pdev)258*4b6cb2b6SLucas Stach static int dw_hdmi_imx_remove(struct platform_device *pdev)
259*4b6cb2b6SLucas Stach {
260*4b6cb2b6SLucas Stach struct imx_hdmi *hdmi = platform_get_drvdata(pdev);
261*4b6cb2b6SLucas Stach
262*4b6cb2b6SLucas Stach component_del(&pdev->dev, &dw_hdmi_imx_ops);
263*4b6cb2b6SLucas Stach dw_hdmi_remove(hdmi->hdmi);
264*4b6cb2b6SLucas Stach
265*4b6cb2b6SLucas Stach return 0;
266*4b6cb2b6SLucas Stach }
267*4b6cb2b6SLucas Stach
268*4b6cb2b6SLucas Stach static struct platform_driver dw_hdmi_imx_platform_driver = {
269*4b6cb2b6SLucas Stach .probe = dw_hdmi_imx_probe,
270*4b6cb2b6SLucas Stach .remove = dw_hdmi_imx_remove,
271*4b6cb2b6SLucas Stach .driver = {
272*4b6cb2b6SLucas Stach .name = "dwhdmi-imx",
273*4b6cb2b6SLucas Stach .of_match_table = dw_hdmi_imx_dt_ids,
274*4b6cb2b6SLucas Stach },
275*4b6cb2b6SLucas Stach };
276*4b6cb2b6SLucas Stach
277*4b6cb2b6SLucas Stach module_platform_driver(dw_hdmi_imx_platform_driver);
278*4b6cb2b6SLucas Stach
279*4b6cb2b6SLucas Stach MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
280*4b6cb2b6SLucas Stach MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
281*4b6cb2b6SLucas Stach MODULE_DESCRIPTION("IMX6 Specific DW-HDMI Driver Extension");
282*4b6cb2b6SLucas Stach MODULE_LICENSE("GPL");
283*4b6cb2b6SLucas Stach MODULE_ALIAS("platform:dwhdmi-imx");
284