]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
098988cb SG |
2 | /* |
3 | * Copyright 2017 Linaro Ltd. | |
4 | * Copyright 2017 ZTE Corporation. | |
098988cb SG |
5 | */ |
6 | ||
7 | #include <linux/clk.h> | |
8 | #include <linux/component.h> | |
9 | #include <linux/mfd/syscon.h> | |
647d295b SR |
10 | #include <linux/module.h> |
11 | #include <linux/platform_device.h> | |
098988cb SG |
12 | #include <linux/regmap.h> |
13 | ||
14 | #include <drm/drm_atomic_helper.h> | |
647d295b | 15 | #include <drm/drm_print.h> |
fcd70cd3 | 16 | #include <drm/drm_probe_helper.h> |
098988cb SG |
17 | |
18 | #include "zx_drm_drv.h" | |
19 | #include "zx_tvenc_regs.h" | |
20 | #include "zx_vou.h" | |
21 | ||
22 | struct zx_tvenc_pwrctrl { | |
23 | struct regmap *regmap; | |
24 | u32 reg; | |
25 | u32 mask; | |
26 | }; | |
27 | ||
28 | struct zx_tvenc { | |
29 | struct drm_connector connector; | |
30 | struct drm_encoder encoder; | |
31 | struct device *dev; | |
32 | void __iomem *mmio; | |
33 | const struct vou_inf *inf; | |
34 | struct zx_tvenc_pwrctrl pwrctrl; | |
35 | }; | |
36 | ||
37 | #define to_zx_tvenc(x) container_of(x, struct zx_tvenc, x) | |
38 | ||
39 | struct zx_tvenc_mode { | |
40 | struct drm_display_mode mode; | |
41 | u32 video_info; | |
42 | u32 video_res; | |
43 | u32 field1_param; | |
44 | u32 field2_param; | |
45 | u32 burst_line_odd1; | |
46 | u32 burst_line_even1; | |
47 | u32 burst_line_odd2; | |
48 | u32 burst_line_even2; | |
49 | u32 line_timing_param; | |
50 | u32 weight_value; | |
51 | u32 blank_black_level; | |
52 | u32 burst_level; | |
53 | u32 control_param; | |
54 | u32 sub_carrier_phase1; | |
55 | u32 phase_line_incr_cvbs; | |
56 | }; | |
57 | ||
58 | /* | |
59 | * The CRM cannot directly provide a suitable frequency, and we have to | |
60 | * ask a multiplied rate from CRM and use the divider in VOU to get the | |
61 | * desired one. | |
62 | */ | |
63 | #define TVENC_CLOCK_MULTIPLIER 4 | |
64 | ||
65 | static const struct zx_tvenc_mode tvenc_mode_pal = { | |
66 | .mode = { | |
67 | .clock = 13500 * TVENC_CLOCK_MULTIPLIER, | |
68 | .hdisplay = 720, | |
69 | .hsync_start = 720 + 12, | |
70 | .hsync_end = 720 + 12 + 2, | |
71 | .htotal = 720 + 12 + 2 + 130, | |
72 | .vdisplay = 576, | |
73 | .vsync_start = 576 + 2, | |
74 | .vsync_end = 576 + 2 + 2, | |
75 | .vtotal = 576 + 2 + 2 + 20, | |
76 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | | |
77 | DRM_MODE_FLAG_INTERLACE, | |
78 | }, | |
79 | .video_info = 0x00040040, | |
80 | .video_res = 0x05a9c760, | |
81 | .field1_param = 0x0004d416, | |
82 | .field2_param = 0x0009b94f, | |
83 | .burst_line_odd1 = 0x0004d406, | |
84 | .burst_line_even1 = 0x0009b53e, | |
85 | .burst_line_odd2 = 0x0004d805, | |
86 | .burst_line_even2 = 0x0009b93f, | |
87 | .line_timing_param = 0x06a96fdf, | |
88 | .weight_value = 0x00c188a0, | |
89 | .blank_black_level = 0x0000fcfc, | |
90 | .burst_level = 0x00001595, | |
91 | .control_param = 0x00000001, | |
92 | .sub_carrier_phase1 = 0x1504c566, | |
93 | .phase_line_incr_cvbs = 0xc068db8c, | |
94 | }; | |
95 | ||
96 | static const struct zx_tvenc_mode tvenc_mode_ntsc = { | |
97 | .mode = { | |
98 | .clock = 13500 * TVENC_CLOCK_MULTIPLIER, | |
99 | .hdisplay = 720, | |
100 | .hsync_start = 720 + 16, | |
101 | .hsync_end = 720 + 16 + 2, | |
102 | .htotal = 720 + 16 + 2 + 120, | |
103 | .vdisplay = 480, | |
104 | .vsync_start = 480 + 3, | |
105 | .vsync_end = 480 + 3 + 2, | |
106 | .vtotal = 480 + 3 + 2 + 17, | |
107 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | | |
108 | DRM_MODE_FLAG_INTERLACE, | |
109 | }, | |
110 | .video_info = 0x00040080, | |
111 | .video_res = 0x05a8375a, | |
112 | .field1_param = 0x00041817, | |
113 | .field2_param = 0x0008351e, | |
114 | .burst_line_odd1 = 0x00041006, | |
115 | .burst_line_even1 = 0x0008290d, | |
116 | .burst_line_odd2 = 0x00000000, | |
117 | .burst_line_even2 = 0x00000000, | |
118 | .line_timing_param = 0x06a8ef9e, | |
119 | .weight_value = 0x00b68197, | |
120 | .blank_black_level = 0x0000f0f0, | |
121 | .burst_level = 0x0000009c, | |
122 | .control_param = 0x00000001, | |
123 | .sub_carrier_phase1 = 0x10f83e10, | |
124 | .phase_line_incr_cvbs = 0x80000000, | |
125 | }; | |
126 | ||
127 | static const struct zx_tvenc_mode *tvenc_modes[] = { | |
128 | &tvenc_mode_pal, | |
129 | &tvenc_mode_ntsc, | |
130 | }; | |
131 | ||
132 | static const struct zx_tvenc_mode * | |
133 | zx_tvenc_find_zmode(struct drm_display_mode *mode) | |
134 | { | |
135 | int i; | |
136 | ||
137 | for (i = 0; i < ARRAY_SIZE(tvenc_modes); i++) { | |
138 | const struct zx_tvenc_mode *zmode = tvenc_modes[i]; | |
139 | ||
140 | if (drm_mode_equal(mode, &zmode->mode)) | |
141 | return zmode; | |
142 | } | |
143 | ||
144 | return NULL; | |
145 | } | |
146 | ||
147 | static void zx_tvenc_encoder_mode_set(struct drm_encoder *encoder, | |
148 | struct drm_display_mode *mode, | |
149 | struct drm_display_mode *adj_mode) | |
150 | { | |
151 | struct zx_tvenc *tvenc = to_zx_tvenc(encoder); | |
152 | const struct zx_tvenc_mode *zmode; | |
153 | struct vou_div_config configs[] = { | |
154 | { VOU_DIV_INF, VOU_DIV_4 }, | |
155 | { VOU_DIV_TVENC, VOU_DIV_1 }, | |
156 | { VOU_DIV_LAYER, VOU_DIV_2 }, | |
157 | }; | |
158 | ||
159 | zx_vou_config_dividers(encoder->crtc, configs, ARRAY_SIZE(configs)); | |
160 | ||
161 | zmode = zx_tvenc_find_zmode(mode); | |
162 | if (!zmode) { | |
163 | DRM_DEV_ERROR(tvenc->dev, "failed to find zmode\n"); | |
164 | return; | |
165 | } | |
166 | ||
167 | zx_writel(tvenc->mmio + VENC_VIDEO_INFO, zmode->video_info); | |
168 | zx_writel(tvenc->mmio + VENC_VIDEO_RES, zmode->video_res); | |
169 | zx_writel(tvenc->mmio + VENC_FIELD1_PARAM, zmode->field1_param); | |
170 | zx_writel(tvenc->mmio + VENC_FIELD2_PARAM, zmode->field2_param); | |
171 | zx_writel(tvenc->mmio + VENC_LINE_O_1, zmode->burst_line_odd1); | |
172 | zx_writel(tvenc->mmio + VENC_LINE_E_1, zmode->burst_line_even1); | |
173 | zx_writel(tvenc->mmio + VENC_LINE_O_2, zmode->burst_line_odd2); | |
174 | zx_writel(tvenc->mmio + VENC_LINE_E_2, zmode->burst_line_even2); | |
175 | zx_writel(tvenc->mmio + VENC_LINE_TIMING_PARAM, | |
176 | zmode->line_timing_param); | |
177 | zx_writel(tvenc->mmio + VENC_WEIGHT_VALUE, zmode->weight_value); | |
178 | zx_writel(tvenc->mmio + VENC_BLANK_BLACK_LEVEL, | |
179 | zmode->blank_black_level); | |
180 | zx_writel(tvenc->mmio + VENC_BURST_LEVEL, zmode->burst_level); | |
181 | zx_writel(tvenc->mmio + VENC_CONTROL_PARAM, zmode->control_param); | |
182 | zx_writel(tvenc->mmio + VENC_SUB_CARRIER_PHASE1, | |
183 | zmode->sub_carrier_phase1); | |
184 | zx_writel(tvenc->mmio + VENC_PHASE_LINE_INCR_CVBS, | |
185 | zmode->phase_line_incr_cvbs); | |
186 | } | |
187 | ||
188 | static void zx_tvenc_encoder_enable(struct drm_encoder *encoder) | |
189 | { | |
190 | struct zx_tvenc *tvenc = to_zx_tvenc(encoder); | |
191 | struct zx_tvenc_pwrctrl *pwrctrl = &tvenc->pwrctrl; | |
192 | ||
193 | /* Set bit to power up TVENC DAC */ | |
194 | regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask, | |
195 | pwrctrl->mask); | |
196 | ||
197 | vou_inf_enable(VOU_TV_ENC, encoder->crtc); | |
198 | ||
199 | zx_writel(tvenc->mmio + VENC_ENABLE, 1); | |
200 | } | |
201 | ||
202 | static void zx_tvenc_encoder_disable(struct drm_encoder *encoder) | |
203 | { | |
204 | struct zx_tvenc *tvenc = to_zx_tvenc(encoder); | |
205 | struct zx_tvenc_pwrctrl *pwrctrl = &tvenc->pwrctrl; | |
206 | ||
207 | zx_writel(tvenc->mmio + VENC_ENABLE, 0); | |
208 | ||
209 | vou_inf_disable(VOU_TV_ENC, encoder->crtc); | |
210 | ||
211 | /* Clear bit to power down TVENC DAC */ | |
212 | regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask, 0); | |
213 | } | |
214 | ||
215 | static const struct drm_encoder_helper_funcs zx_tvenc_encoder_helper_funcs = { | |
216 | .enable = zx_tvenc_encoder_enable, | |
217 | .disable = zx_tvenc_encoder_disable, | |
218 | .mode_set = zx_tvenc_encoder_mode_set, | |
219 | }; | |
220 | ||
221 | static const struct drm_encoder_funcs zx_tvenc_encoder_funcs = { | |
222 | .destroy = drm_encoder_cleanup, | |
223 | }; | |
224 | ||
225 | static int zx_tvenc_connector_get_modes(struct drm_connector *connector) | |
226 | { | |
227 | struct zx_tvenc *tvenc = to_zx_tvenc(connector); | |
228 | struct device *dev = tvenc->dev; | |
229 | int i; | |
230 | ||
231 | for (i = 0; i < ARRAY_SIZE(tvenc_modes); i++) { | |
232 | const struct zx_tvenc_mode *zmode = tvenc_modes[i]; | |
233 | struct drm_display_mode *mode; | |
234 | ||
235 | mode = drm_mode_duplicate(connector->dev, &zmode->mode); | |
236 | if (!mode) { | |
237 | DRM_DEV_ERROR(dev, "failed to duplicate drm mode\n"); | |
238 | continue; | |
239 | } | |
240 | ||
241 | drm_mode_set_name(mode); | |
242 | drm_mode_probed_add(connector, mode); | |
243 | } | |
244 | ||
245 | return i; | |
246 | } | |
247 | ||
248 | static enum drm_mode_status | |
249 | zx_tvenc_connector_mode_valid(struct drm_connector *connector, | |
250 | struct drm_display_mode *mode) | |
251 | { | |
252 | struct zx_tvenc *tvenc = to_zx_tvenc(connector); | |
253 | const struct zx_tvenc_mode *zmode; | |
254 | ||
255 | zmode = zx_tvenc_find_zmode(mode); | |
256 | if (!zmode) { | |
257 | DRM_DEV_ERROR(tvenc->dev, "unsupported mode: %s\n", mode->name); | |
258 | return MODE_NOMODE; | |
259 | } | |
260 | ||
261 | return MODE_OK; | |
262 | } | |
263 | ||
264 | static struct drm_connector_helper_funcs zx_tvenc_connector_helper_funcs = { | |
265 | .get_modes = zx_tvenc_connector_get_modes, | |
266 | .mode_valid = zx_tvenc_connector_mode_valid, | |
267 | }; | |
268 | ||
269 | static const struct drm_connector_funcs zx_tvenc_connector_funcs = { | |
098988cb SG |
270 | .fill_modes = drm_helper_probe_single_connector_modes, |
271 | .destroy = drm_connector_cleanup, | |
272 | .reset = drm_atomic_helper_connector_reset, | |
273 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | |
274 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | |
275 | }; | |
276 | ||
277 | static int zx_tvenc_register(struct drm_device *drm, struct zx_tvenc *tvenc) | |
278 | { | |
279 | struct drm_encoder *encoder = &tvenc->encoder; | |
280 | struct drm_connector *connector = &tvenc->connector; | |
281 | ||
282 | /* | |
283 | * The tvenc is designed to use aux channel, as there is a deflicker | |
284 | * block for the channel. | |
285 | */ | |
286 | encoder->possible_crtcs = BIT(1); | |
287 | ||
288 | drm_encoder_init(drm, encoder, &zx_tvenc_encoder_funcs, | |
289 | DRM_MODE_ENCODER_TVDAC, NULL); | |
290 | drm_encoder_helper_add(encoder, &zx_tvenc_encoder_helper_funcs); | |
291 | ||
292 | connector->interlace_allowed = true; | |
293 | ||
294 | drm_connector_init(drm, connector, &zx_tvenc_connector_funcs, | |
295 | DRM_MODE_CONNECTOR_Composite); | |
296 | drm_connector_helper_add(connector, &zx_tvenc_connector_helper_funcs); | |
297 | ||
cde4c44d | 298 | drm_connector_attach_encoder(connector, encoder); |
098988cb SG |
299 | |
300 | return 0; | |
301 | } | |
302 | ||
303 | static int zx_tvenc_pwrctrl_init(struct zx_tvenc *tvenc) | |
304 | { | |
305 | struct zx_tvenc_pwrctrl *pwrctrl = &tvenc->pwrctrl; | |
306 | struct device *dev = tvenc->dev; | |
307 | struct of_phandle_args out_args; | |
308 | struct regmap *regmap; | |
309 | int ret; | |
310 | ||
311 | ret = of_parse_phandle_with_fixed_args(dev->of_node, | |
312 | "zte,tvenc-power-control", 2, 0, &out_args); | |
313 | if (ret) | |
314 | return ret; | |
315 | ||
316 | regmap = syscon_node_to_regmap(out_args.np); | |
317 | if (IS_ERR(regmap)) { | |
318 | ret = PTR_ERR(regmap); | |
319 | goto out; | |
320 | } | |
321 | ||
322 | pwrctrl->regmap = regmap; | |
323 | pwrctrl->reg = out_args.args[0]; | |
324 | pwrctrl->mask = out_args.args[1]; | |
325 | ||
326 | out: | |
327 | of_node_put(out_args.np); | |
328 | return ret; | |
329 | } | |
330 | ||
331 | static int zx_tvenc_bind(struct device *dev, struct device *master, void *data) | |
332 | { | |
333 | struct platform_device *pdev = to_platform_device(dev); | |
334 | struct drm_device *drm = data; | |
335 | struct resource *res; | |
336 | struct zx_tvenc *tvenc; | |
337 | int ret; | |
338 | ||
339 | tvenc = devm_kzalloc(dev, sizeof(*tvenc), GFP_KERNEL); | |
340 | if (!tvenc) | |
341 | return -ENOMEM; | |
342 | ||
343 | tvenc->dev = dev; | |
344 | dev_set_drvdata(dev, tvenc); | |
345 | ||
346 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
347 | tvenc->mmio = devm_ioremap_resource(dev, res); | |
348 | if (IS_ERR(tvenc->mmio)) { | |
349 | ret = PTR_ERR(tvenc->mmio); | |
350 | DRM_DEV_ERROR(dev, "failed to remap tvenc region: %d\n", ret); | |
351 | return ret; | |
352 | } | |
353 | ||
354 | ret = zx_tvenc_pwrctrl_init(tvenc); | |
355 | if (ret) { | |
356 | DRM_DEV_ERROR(dev, "failed to init power control: %d\n", ret); | |
357 | return ret; | |
358 | } | |
359 | ||
360 | ret = zx_tvenc_register(drm, tvenc); | |
361 | if (ret) { | |
362 | DRM_DEV_ERROR(dev, "failed to register tvenc: %d\n", ret); | |
363 | return ret; | |
364 | } | |
365 | ||
366 | return 0; | |
367 | } | |
368 | ||
369 | static void zx_tvenc_unbind(struct device *dev, struct device *master, | |
370 | void *data) | |
371 | { | |
372 | /* Nothing to do */ | |
373 | } | |
374 | ||
375 | static const struct component_ops zx_tvenc_component_ops = { | |
376 | .bind = zx_tvenc_bind, | |
377 | .unbind = zx_tvenc_unbind, | |
378 | }; | |
379 | ||
380 | static int zx_tvenc_probe(struct platform_device *pdev) | |
381 | { | |
382 | return component_add(&pdev->dev, &zx_tvenc_component_ops); | |
383 | } | |
384 | ||
385 | static int zx_tvenc_remove(struct platform_device *pdev) | |
386 | { | |
387 | component_del(&pdev->dev, &zx_tvenc_component_ops); | |
388 | return 0; | |
389 | } | |
390 | ||
391 | static const struct of_device_id zx_tvenc_of_match[] = { | |
392 | { .compatible = "zte,zx296718-tvenc", }, | |
393 | { /* end */ }, | |
394 | }; | |
395 | MODULE_DEVICE_TABLE(of, zx_tvenc_of_match); | |
396 | ||
397 | struct platform_driver zx_tvenc_driver = { | |
398 | .probe = zx_tvenc_probe, | |
399 | .remove = zx_tvenc_remove, | |
400 | .driver = { | |
401 | .name = "zx-tvenc", | |
402 | .of_match_table = zx_tvenc_of_match, | |
403 | }, | |
404 | }; |