1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * HDMI wrapper |
4 | * |
5 | * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com/ |
6 | */ |
7 | |
8 | #define DSS_SUBSYS_NAME "HDMIWP" |
9 | |
10 | #include <linux/kernel.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/seq_file.h> |
15 | |
16 | #include "omapdss.h" |
17 | #include "dss.h" |
18 | #include "hdmi.h" |
19 | |
20 | void hdmi_wp_dump(struct hdmi_wp_data *wp, struct seq_file *s) |
21 | { |
22 | #define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, hdmi_read_reg(wp->base, r)) |
23 | |
24 | DUMPREG(HDMI_WP_REVISION); |
25 | DUMPREG(HDMI_WP_SYSCONFIG); |
26 | DUMPREG(HDMI_WP_IRQSTATUS_RAW); |
27 | DUMPREG(HDMI_WP_IRQSTATUS); |
28 | DUMPREG(HDMI_WP_IRQENABLE_SET); |
29 | DUMPREG(HDMI_WP_IRQENABLE_CLR); |
30 | DUMPREG(HDMI_WP_IRQWAKEEN); |
31 | DUMPREG(HDMI_WP_PWR_CTRL); |
32 | DUMPREG(HDMI_WP_DEBOUNCE); |
33 | DUMPREG(HDMI_WP_VIDEO_CFG); |
34 | DUMPREG(HDMI_WP_VIDEO_SIZE); |
35 | DUMPREG(HDMI_WP_VIDEO_TIMING_H); |
36 | DUMPREG(HDMI_WP_VIDEO_TIMING_V); |
37 | DUMPREG(HDMI_WP_CLK); |
38 | DUMPREG(HDMI_WP_AUDIO_CFG); |
39 | DUMPREG(HDMI_WP_AUDIO_CFG2); |
40 | DUMPREG(HDMI_WP_AUDIO_CTRL); |
41 | DUMPREG(HDMI_WP_AUDIO_DATA); |
42 | } |
43 | |
44 | u32 hdmi_wp_get_irqstatus(struct hdmi_wp_data *wp) |
45 | { |
46 | return hdmi_read_reg(base_addr: wp->base, HDMI_WP_IRQSTATUS); |
47 | } |
48 | |
49 | void hdmi_wp_set_irqstatus(struct hdmi_wp_data *wp, u32 irqstatus) |
50 | { |
51 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_IRQSTATUS, val: irqstatus); |
52 | /* flush posted write */ |
53 | hdmi_read_reg(base_addr: wp->base, HDMI_WP_IRQSTATUS); |
54 | } |
55 | |
56 | void hdmi_wp_set_irqenable(struct hdmi_wp_data *wp, u32 mask) |
57 | { |
58 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_IRQENABLE_SET, val: mask); |
59 | } |
60 | |
61 | void hdmi_wp_clear_irqenable(struct hdmi_wp_data *wp, u32 mask) |
62 | { |
63 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_IRQENABLE_CLR, val: mask); |
64 | } |
65 | |
66 | /* PHY_PWR_CMD */ |
67 | int hdmi_wp_set_phy_pwr(struct hdmi_wp_data *wp, enum hdmi_phy_pwr val) |
68 | { |
69 | /* Return if already the state */ |
70 | if (REG_GET(wp->base, HDMI_WP_PWR_CTRL, 5, 4) == val) |
71 | return 0; |
72 | |
73 | /* Command for power control of HDMI PHY */ |
74 | REG_FLD_MOD(wp->base, HDMI_WP_PWR_CTRL, val, 7, 6); |
75 | |
76 | /* Status of the power control of HDMI PHY */ |
77 | if (hdmi_wait_for_bit_change(base_addr: wp->base, HDMI_WP_PWR_CTRL, b2: 5, b1: 4, val) |
78 | != val) { |
79 | DSSERR("Failed to set PHY power mode to %d\n" , val); |
80 | return -ETIMEDOUT; |
81 | } |
82 | |
83 | return 0; |
84 | } |
85 | |
86 | /* PLL_PWR_CMD */ |
87 | int hdmi_wp_set_pll_pwr(struct hdmi_wp_data *wp, enum hdmi_pll_pwr val) |
88 | { |
89 | /* Command for power control of HDMI PLL */ |
90 | REG_FLD_MOD(wp->base, HDMI_WP_PWR_CTRL, val, 3, 2); |
91 | |
92 | /* wait till PHY_PWR_STATUS is set */ |
93 | if (hdmi_wait_for_bit_change(base_addr: wp->base, HDMI_WP_PWR_CTRL, b2: 1, b1: 0, val) |
94 | != val) { |
95 | DSSERR("Failed to set PLL_PWR_STATUS\n" ); |
96 | return -ETIMEDOUT; |
97 | } |
98 | |
99 | return 0; |
100 | } |
101 | |
102 | int hdmi_wp_video_start(struct hdmi_wp_data *wp) |
103 | { |
104 | REG_FLD_MOD(wp->base, HDMI_WP_VIDEO_CFG, true, 31, 31); |
105 | |
106 | return 0; |
107 | } |
108 | |
109 | void hdmi_wp_video_stop(struct hdmi_wp_data *wp) |
110 | { |
111 | int i; |
112 | |
113 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_IRQSTATUS, HDMI_IRQ_VIDEO_FRAME_DONE); |
114 | |
115 | REG_FLD_MOD(wp->base, HDMI_WP_VIDEO_CFG, false, 31, 31); |
116 | |
117 | for (i = 0; i < 50; ++i) { |
118 | u32 v; |
119 | |
120 | msleep(msecs: 20); |
121 | |
122 | v = hdmi_read_reg(base_addr: wp->base, HDMI_WP_IRQSTATUS_RAW); |
123 | if (v & HDMI_IRQ_VIDEO_FRAME_DONE) |
124 | return; |
125 | } |
126 | |
127 | DSSERR("no HDMI FRAMEDONE when disabling output\n" ); |
128 | } |
129 | |
130 | void hdmi_wp_video_config_format(struct hdmi_wp_data *wp, |
131 | const struct hdmi_video_format *video_fmt) |
132 | { |
133 | u32 l = 0; |
134 | |
135 | REG_FLD_MOD(wp->base, HDMI_WP_VIDEO_CFG, video_fmt->packing_mode, |
136 | 10, 8); |
137 | |
138 | l |= FLD_VAL(video_fmt->y_res, 31, 16); |
139 | l |= FLD_VAL(video_fmt->x_res, 15, 0); |
140 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_VIDEO_SIZE, val: l); |
141 | } |
142 | |
143 | void hdmi_wp_video_config_interface(struct hdmi_wp_data *wp, |
144 | const struct videomode *vm) |
145 | { |
146 | u32 r; |
147 | bool vsync_inv, hsync_inv; |
148 | DSSDBG("Enter hdmi_wp_video_config_interface\n" ); |
149 | |
150 | vsync_inv = !!(vm->flags & DISPLAY_FLAGS_VSYNC_LOW); |
151 | hsync_inv = !!(vm->flags & DISPLAY_FLAGS_HSYNC_LOW); |
152 | |
153 | r = hdmi_read_reg(base_addr: wp->base, HDMI_WP_VIDEO_CFG); |
154 | r = FLD_MOD(r, 1, 7, 7); /* VSYNC_POL to dispc active high */ |
155 | r = FLD_MOD(r, 1, 6, 6); /* HSYNC_POL to dispc active high */ |
156 | r = FLD_MOD(r, vsync_inv, 5, 5); /* CORE_VSYNC_INV */ |
157 | r = FLD_MOD(r, hsync_inv, 4, 4); /* CORE_HSYNC_INV */ |
158 | r = FLD_MOD(r, !!(vm->flags & DISPLAY_FLAGS_INTERLACED), 3, 3); |
159 | r = FLD_MOD(r, 1, 1, 0); /* HDMI_TIMING_MASTER_24BIT */ |
160 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_VIDEO_CFG, val: r); |
161 | } |
162 | |
163 | void hdmi_wp_video_config_timing(struct hdmi_wp_data *wp, |
164 | const struct videomode *vm) |
165 | { |
166 | u32 timing_h = 0; |
167 | u32 timing_v = 0; |
168 | unsigned int hsync_len_offset = 1; |
169 | |
170 | DSSDBG("Enter hdmi_wp_video_config_timing\n" ); |
171 | |
172 | /* |
173 | * On OMAP4 and OMAP5 ES1 the HSW field is programmed as is. On OMAP5 |
174 | * ES2+ (including DRA7/AM5 SoCs) HSW field is programmed to hsync_len-1. |
175 | * However, we don't support OMAP5 ES1 at all, so we can just check for |
176 | * OMAP4 here. |
177 | */ |
178 | if (wp->version == 4) |
179 | hsync_len_offset = 0; |
180 | |
181 | timing_h |= FLD_VAL(vm->hback_porch, 31, 20); |
182 | timing_h |= FLD_VAL(vm->hfront_porch, 19, 8); |
183 | timing_h |= FLD_VAL(vm->hsync_len - hsync_len_offset, 7, 0); |
184 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_VIDEO_TIMING_H, val: timing_h); |
185 | |
186 | timing_v |= FLD_VAL(vm->vback_porch, 31, 20); |
187 | timing_v |= FLD_VAL(vm->vfront_porch, 19, 8); |
188 | timing_v |= FLD_VAL(vm->vsync_len, 7, 0); |
189 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_VIDEO_TIMING_V, val: timing_v); |
190 | } |
191 | |
192 | void hdmi_wp_init_vid_fmt_timings(struct hdmi_video_format *video_fmt, |
193 | struct videomode *vm, const struct hdmi_config *param) |
194 | { |
195 | DSSDBG("Enter hdmi_wp_video_init_format\n" ); |
196 | |
197 | video_fmt->packing_mode = HDMI_PACK_10b_RGB_YUV444; |
198 | video_fmt->y_res = param->vm.vactive; |
199 | video_fmt->x_res = param->vm.hactive; |
200 | |
201 | vm->hback_porch = param->vm.hback_porch; |
202 | vm->hfront_porch = param->vm.hfront_porch; |
203 | vm->hsync_len = param->vm.hsync_len; |
204 | vm->vback_porch = param->vm.vback_porch; |
205 | vm->vfront_porch = param->vm.vfront_porch; |
206 | vm->vsync_len = param->vm.vsync_len; |
207 | |
208 | vm->flags = param->vm.flags; |
209 | |
210 | if (param->vm.flags & DISPLAY_FLAGS_INTERLACED) { |
211 | video_fmt->y_res /= 2; |
212 | vm->vback_porch /= 2; |
213 | vm->vfront_porch /= 2; |
214 | vm->vsync_len /= 2; |
215 | } |
216 | |
217 | if (param->vm.flags & DISPLAY_FLAGS_DOUBLECLK) { |
218 | video_fmt->x_res *= 2; |
219 | vm->hfront_porch *= 2; |
220 | vm->hsync_len *= 2; |
221 | vm->hback_porch *= 2; |
222 | } |
223 | } |
224 | |
225 | void hdmi_wp_audio_config_format(struct hdmi_wp_data *wp, |
226 | struct hdmi_audio_format *aud_fmt) |
227 | { |
228 | u32 r; |
229 | |
230 | DSSDBG("Enter hdmi_wp_audio_config_format\n" ); |
231 | |
232 | r = hdmi_read_reg(base_addr: wp->base, HDMI_WP_AUDIO_CFG); |
233 | if (wp->version == 4) { |
234 | r = FLD_MOD(r, aud_fmt->stereo_channels, 26, 24); |
235 | r = FLD_MOD(r, aud_fmt->active_chnnls_msk, 23, 16); |
236 | } |
237 | r = FLD_MOD(r, aud_fmt->en_sig_blk_strt_end, 5, 5); |
238 | r = FLD_MOD(r, aud_fmt->type, 4, 4); |
239 | r = FLD_MOD(r, aud_fmt->justification, 3, 3); |
240 | r = FLD_MOD(r, aud_fmt->sample_order, 2, 2); |
241 | r = FLD_MOD(r, aud_fmt->samples_per_word, 1, 1); |
242 | r = FLD_MOD(r, aud_fmt->sample_size, 0, 0); |
243 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_AUDIO_CFG, val: r); |
244 | } |
245 | |
246 | void hdmi_wp_audio_config_dma(struct hdmi_wp_data *wp, |
247 | struct hdmi_audio_dma *aud_dma) |
248 | { |
249 | u32 r; |
250 | |
251 | DSSDBG("Enter hdmi_wp_audio_config_dma\n" ); |
252 | |
253 | r = hdmi_read_reg(base_addr: wp->base, HDMI_WP_AUDIO_CFG2); |
254 | r = FLD_MOD(r, aud_dma->transfer_size, 15, 8); |
255 | r = FLD_MOD(r, aud_dma->block_size, 7, 0); |
256 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_AUDIO_CFG2, val: r); |
257 | |
258 | r = hdmi_read_reg(base_addr: wp->base, HDMI_WP_AUDIO_CTRL); |
259 | r = FLD_MOD(r, aud_dma->mode, 9, 9); |
260 | r = FLD_MOD(r, aud_dma->fifo_threshold, 8, 0); |
261 | hdmi_write_reg(base_addr: wp->base, HDMI_WP_AUDIO_CTRL, val: r); |
262 | } |
263 | |
264 | int hdmi_wp_audio_enable(struct hdmi_wp_data *wp, bool enable) |
265 | { |
266 | REG_FLD_MOD(wp->base, HDMI_WP_AUDIO_CTRL, enable, 31, 31); |
267 | |
268 | return 0; |
269 | } |
270 | |
271 | int hdmi_wp_audio_core_req_enable(struct hdmi_wp_data *wp, bool enable) |
272 | { |
273 | REG_FLD_MOD(wp->base, HDMI_WP_AUDIO_CTRL, enable, 30, 30); |
274 | |
275 | return 0; |
276 | } |
277 | |
278 | int hdmi_wp_init(struct platform_device *pdev, struct hdmi_wp_data *wp, |
279 | unsigned int version) |
280 | { |
281 | struct resource *res; |
282 | |
283 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wp" ); |
284 | wp->base = devm_ioremap_resource(dev: &pdev->dev, res); |
285 | if (IS_ERR(ptr: wp->base)) |
286 | return PTR_ERR(ptr: wp->base); |
287 | |
288 | wp->phys_base = res->start; |
289 | wp->version = version; |
290 | |
291 | return 0; |
292 | } |
293 | |
294 | phys_addr_t hdmi_wp_get_audio_dma_addr(struct hdmi_wp_data *wp) |
295 | { |
296 | return wp->phys_base + HDMI_WP_AUDIO_DATA; |
297 | } |
298 | |