Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1/*
  2 * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
  3 *
  4 * This program is free software; you can redistribute it and/or modify
  5 * it under the terms of the GNU General Public License as published by
  6 * the Free Software Foundation; either version 2 of the License, or
  7 * (at your option) any later version.
  8 */
  9
 10#include <linux/module.h>
 11#include <linux/platform_device.h>
 12#include <linux/mfd/syscon.h>
 13#include <linux/regmap.h>
 14#include <drm/drm_of.h>
 15#include <drm/drmP.h>
 16#include <drm/drm_crtc_helper.h>
 17#include <drm/drm_edid.h>
 18#include <drm/bridge/dw_hdmi.h>
 19
 20#include "rockchip_drm_drv.h"
 21#include "rockchip_drm_vop.h"
 22
 23#define GRF_SOC_CON6                    0x025c
 24#define HDMI_SEL_VOP_LIT                (1 << 4)
 25
 26struct rockchip_hdmi {
 27	struct device *dev;
 28	struct regmap *regmap;
 29	struct drm_encoder encoder;
 30};
 31
 32#define to_rockchip_hdmi(x)	container_of(x, struct rockchip_hdmi, x)
 33
 34static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
 35	{
 36		27000000, {
 37			{ 0x00b3, 0x0000},
 38			{ 0x2153, 0x0000},
 39			{ 0x40f3, 0x0000}
 40		},
 41	}, {
 42		36000000, {
 43			{ 0x00b3, 0x0000},
 44			{ 0x2153, 0x0000},
 45			{ 0x40f3, 0x0000}
 46		},
 47	}, {
 48		40000000, {
 49			{ 0x00b3, 0x0000},
 50			{ 0x2153, 0x0000},
 51			{ 0x40f3, 0x0000}
 52		},
 53	}, {
 54		54000000, {
 55			{ 0x0072, 0x0001},
 56			{ 0x2142, 0x0001},
 57			{ 0x40a2, 0x0001},
 58		},
 59	}, {
 60		65000000, {
 61			{ 0x0072, 0x0001},
 62			{ 0x2142, 0x0001},
 63			{ 0x40a2, 0x0001},
 64		},
 65	}, {
 66		66000000, {
 67			{ 0x013e, 0x0003},
 68			{ 0x217e, 0x0002},
 69			{ 0x4061, 0x0002}
 70		},
 71	}, {
 72		74250000, {
 73			{ 0x0072, 0x0001},
 74			{ 0x2145, 0x0002},
 75			{ 0x4061, 0x0002}
 76		},
 77	}, {
 78		83500000, {
 79			{ 0x0072, 0x0001},
 80		},
 81	}, {
 82		108000000, {
 83			{ 0x0051, 0x0002},
 84			{ 0x2145, 0x0002},
 85			{ 0x4061, 0x0002}
 86		},
 87	}, {
 88		106500000, {
 89			{ 0x0051, 0x0002},
 90			{ 0x2145, 0x0002},
 91			{ 0x4061, 0x0002}
 92		},
 93	}, {
 94		146250000, {
 95			{ 0x0051, 0x0002},
 96			{ 0x2145, 0x0002},
 97			{ 0x4061, 0x0002}
 98		},
 99	}, {
100		148500000, {
101			{ 0x0051, 0x0003},
102			{ 0x214c, 0x0003},
103			{ 0x4064, 0x0003}
104		},
105	}, {
106		~0UL, {
107			{ 0x00a0, 0x000a },
108			{ 0x2001, 0x000f },
109			{ 0x4002, 0x000f },
110		},
111	}
112};
113
114static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
115	/*      pixelclk    bpp8    bpp10   bpp12 */
116	{
117		40000000,  { 0x0018, 0x0018, 0x0018 },
118	}, {
119		65000000,  { 0x0028, 0x0028, 0x0028 },
120	}, {
121		66000000,  { 0x0038, 0x0038, 0x0038 },
122	}, {
123		74250000,  { 0x0028, 0x0038, 0x0038 },
124	}, {
125		83500000,  { 0x0028, 0x0038, 0x0038 },
126	}, {
127		146250000, { 0x0038, 0x0038, 0x0038 },
128	}, {
129		148500000, { 0x0000, 0x0038, 0x0038 },
130	}, {
131		~0UL,      { 0x0000, 0x0000, 0x0000},
132	}
133};
134
135static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
136	/*pixelclk   symbol   term   vlev*/
137	{ 74250000,  0x8009, 0x0004, 0x0272},
138	{ 148500000, 0x802b, 0x0004, 0x028d},
139	{ 297000000, 0x8039, 0x0005, 0x028d},
140	{ ~0UL,	     0x0000, 0x0000, 0x0000}
141};
142
143static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
144{
145	struct device_node *np = hdmi->dev->of_node;
146
147	hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
148	if (IS_ERR(hdmi->regmap)) {
149		dev_err(hdmi->dev, "Unable to get rockchip,grf\n");
150		return PTR_ERR(hdmi->regmap);
151	}
152
153	return 0;
154}
155
156static enum drm_mode_status
157dw_hdmi_rockchip_mode_valid(struct drm_connector *connector,
158			    struct drm_display_mode *mode)
159{
160	const struct dw_hdmi_mpll_config *mpll_cfg = rockchip_mpll_cfg;
161	int pclk = mode->clock * 1000;
162	bool valid = false;
163	int i;
164
165	for (i = 0; mpll_cfg[i].mpixelclock != (~0UL); i++) {
166		if (pclk == mpll_cfg[i].mpixelclock) {
167			valid = true;
168			break;
169		}
170	}
171
172	return (valid) ? MODE_OK : MODE_BAD;
173}
174
175static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = {
176	.destroy = drm_encoder_cleanup,
177};
178
179static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder)
180{
181}
182
183static bool
184dw_hdmi_rockchip_encoder_mode_fixup(struct drm_encoder *encoder,
185				    const struct drm_display_mode *mode,
186				    struct drm_display_mode *adj_mode)
187{
188	return true;
189}
190
191static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
192					      struct drm_display_mode *mode,
193					      struct drm_display_mode *adj_mode)
194{
195}
196
197static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
198{
199	struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
200	u32 val;
201	int mux;
202
203	mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder);
204	if (mux)
205		val = HDMI_SEL_VOP_LIT | (HDMI_SEL_VOP_LIT << 16);
206	else
207		val = HDMI_SEL_VOP_LIT << 16;
208
209	regmap_write(hdmi->regmap, GRF_SOC_CON6, val);
210	dev_dbg(hdmi->dev, "vop %s output to hdmi\n",
211		(mux) ? "LIT" : "BIG");
212}
213
214static int
215dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
216				      struct drm_crtc_state *crtc_state,
217				      struct drm_connector_state *conn_state)
218{
219	struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
220
221	s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
222	s->output_type = DRM_MODE_CONNECTOR_HDMIA;
223
224	return 0;
225}
226
227static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = {
228	.mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup,
229	.mode_set   = dw_hdmi_rockchip_encoder_mode_set,
230	.enable     = dw_hdmi_rockchip_encoder_enable,
231	.disable    = dw_hdmi_rockchip_encoder_disable,
232	.atomic_check = dw_hdmi_rockchip_encoder_atomic_check,
233};
234
235static const struct dw_hdmi_plat_data rockchip_hdmi_drv_data = {
236	.mode_valid = dw_hdmi_rockchip_mode_valid,
237	.mpll_cfg   = rockchip_mpll_cfg,
238	.cur_ctr    = rockchip_cur_ctr,
239	.phy_config = rockchip_phy_config,
240	.dev_type   = RK3288_HDMI,
241};
242
243static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
244	{ .compatible = "rockchip,rk3288-dw-hdmi",
245	  .data = &rockchip_hdmi_drv_data
246	},
247	{},
248};
249MODULE_DEVICE_TABLE(of, dw_hdmi_rockchip_dt_ids);
250
251static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
252				 void *data)
253{
254	struct platform_device *pdev = to_platform_device(dev);
255	const struct dw_hdmi_plat_data *plat_data;
256	const struct of_device_id *match;
257	struct drm_device *drm = data;
258	struct drm_encoder *encoder;
259	struct rockchip_hdmi *hdmi;
260	struct resource *iores;
261	int irq;
262	int ret;
263
264	if (!pdev->dev.of_node)
265		return -ENODEV;
266
267	hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
268	if (!hdmi)
269		return -ENOMEM;
270
271	match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node);
272	plat_data = match->data;
273	hdmi->dev = &pdev->dev;
274	encoder = &hdmi->encoder;
275
276	irq = platform_get_irq(pdev, 0);
277	if (irq < 0)
278		return irq;
279
280	iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
281	if (!iores)
282		return -ENXIO;
283
284	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
285	/*
286	 * If we failed to find the CRTC(s) which this encoder is
287	 * supposed to be connected to, it's because the CRTC has
288	 * not been registered yet.  Defer probing, and hope that
289	 * the required CRTC is added later.
290	 */
291	if (encoder->possible_crtcs == 0)
292		return -EPROBE_DEFER;
293
294	ret = rockchip_hdmi_parse_dt(hdmi);
295	if (ret) {
296		dev_err(hdmi->dev, "Unable to parse OF data\n");
297		return ret;
298	}
299
300	drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
301	drm_encoder_init(drm, encoder, &dw_hdmi_rockchip_encoder_funcs,
302			 DRM_MODE_ENCODER_TMDS, NULL);
303
304	ret = dw_hdmi_bind(dev, master, data, encoder, iores, irq, plat_data);
305
306	/*
307	 * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
308	 * which would have called the encoder cleanup.  Do it manually.
309	 */
310	if (ret)
311		drm_encoder_cleanup(encoder);
312
313	return ret;
314}
315
316static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
317				    void *data)
318{
319	return dw_hdmi_unbind(dev, master, data);
320}
321
322static const struct component_ops dw_hdmi_rockchip_ops = {
323	.bind	= dw_hdmi_rockchip_bind,
324	.unbind	= dw_hdmi_rockchip_unbind,
325};
326
327static int dw_hdmi_rockchip_probe(struct platform_device *pdev)
328{
329	return component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
330}
331
332static int dw_hdmi_rockchip_remove(struct platform_device *pdev)
333{
334	component_del(&pdev->dev, &dw_hdmi_rockchip_ops);
335
336	return 0;
337}
338
339static struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
340	.probe  = dw_hdmi_rockchip_probe,
341	.remove = dw_hdmi_rockchip_remove,
342	.driver = {
343		.name = "dwhdmi-rockchip",
344		.of_match_table = dw_hdmi_rockchip_dt_ids,
345	},
346};
347
348module_platform_driver(dw_hdmi_rockchip_pltfm_driver);
349
350MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
351MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
352MODULE_DESCRIPTION("Rockchip Specific DW-HDMI Driver Extension");
353MODULE_LICENSE("GPL");
354MODULE_ALIAS("platform:dwhdmi-rockchip");