1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /************************************************************************** |
3 | * Copyright (c) 2011, Intel Corporation. |
4 | * All Rights Reserved. |
5 | * |
6 | **************************************************************************/ |
7 | |
8 | #include <linux/delay.h> |
9 | |
10 | #include <drm/drm.h> |
11 | #include <drm/drm_crtc_helper.h> |
12 | |
13 | #include "cdv_device.h" |
14 | #include "gma_device.h" |
15 | #include "intel_bios.h" |
16 | #include "psb_drv.h" |
17 | #include "psb_intel_reg.h" |
18 | #include "psb_reg.h" |
19 | |
20 | #define VGA_SR_INDEX 0x3c4 |
21 | #define VGA_SR_DATA 0x3c5 |
22 | |
23 | static void cdv_disable_vga(struct drm_device *dev) |
24 | { |
25 | u8 sr1; |
26 | u32 vga_reg; |
27 | |
28 | vga_reg = VGACNTRL; |
29 | |
30 | outb(value: 1, VGA_SR_INDEX); |
31 | sr1 = inb(VGA_SR_DATA); |
32 | outb(value: sr1 | 1<<5, VGA_SR_DATA); |
33 | udelay(300); |
34 | |
35 | REG_WRITE(vga_reg, VGA_DISP_DISABLE); |
36 | REG_READ(vga_reg); |
37 | } |
38 | |
39 | static int cdv_output_init(struct drm_device *dev) |
40 | { |
41 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
42 | |
43 | drm_mode_create_scaling_mode_property(dev); |
44 | |
45 | cdv_disable_vga(dev); |
46 | |
47 | cdv_intel_crt_init(dev, mode_dev: &dev_priv->mode_dev); |
48 | cdv_intel_lvds_init(dev, mode_dev: &dev_priv->mode_dev); |
49 | |
50 | /* These bits indicate HDMI not SDVO on CDV */ |
51 | if (REG_READ(SDVOB) & SDVO_DETECTED) { |
52 | cdv_hdmi_init(dev, mode_dev: &dev_priv->mode_dev, SDVOB); |
53 | if (REG_READ(DP_B) & DP_DETECTED) |
54 | cdv_intel_dp_init(dev, mode_dev: &dev_priv->mode_dev, DP_B); |
55 | } |
56 | |
57 | if (REG_READ(SDVOC) & SDVO_DETECTED) { |
58 | cdv_hdmi_init(dev, mode_dev: &dev_priv->mode_dev, SDVOC); |
59 | if (REG_READ(DP_C) & DP_DETECTED) |
60 | cdv_intel_dp_init(dev, mode_dev: &dev_priv->mode_dev, DP_C); |
61 | } |
62 | return 0; |
63 | } |
64 | |
65 | /* |
66 | * Cedartrail Backlght Interfaces |
67 | */ |
68 | |
69 | static int cdv_backlight_combination_mode(struct drm_device *dev) |
70 | { |
71 | return REG_READ(BLC_PWM_CTL2) & PWM_LEGACY_MODE; |
72 | } |
73 | |
74 | static u32 cdv_get_max_backlight(struct drm_device *dev) |
75 | { |
76 | u32 max = REG_READ(BLC_PWM_CTL); |
77 | |
78 | if (max == 0) { |
79 | DRM_DEBUG_KMS("LVDS Panel PWM value is 0!\n" ); |
80 | /* i915 does this, I believe which means that we should not |
81 | * smash PWM control as firmware will take control of it. */ |
82 | return 1; |
83 | } |
84 | |
85 | max >>= 16; |
86 | if (cdv_backlight_combination_mode(dev)) |
87 | max *= 0xff; |
88 | return max; |
89 | } |
90 | |
91 | static int cdv_get_brightness(struct drm_device *dev) |
92 | { |
93 | struct pci_dev *pdev = to_pci_dev(dev->dev); |
94 | u32 val = REG_READ(BLC_PWM_CTL) & BACKLIGHT_DUTY_CYCLE_MASK; |
95 | |
96 | if (cdv_backlight_combination_mode(dev)) { |
97 | u8 lbpc; |
98 | |
99 | val &= ~1; |
100 | pci_read_config_byte(dev: pdev, where: 0xF4, val: &lbpc); |
101 | val *= lbpc; |
102 | } |
103 | return (val * 100)/cdv_get_max_backlight(dev); |
104 | } |
105 | |
106 | static void cdv_set_brightness(struct drm_device *dev, int level) |
107 | { |
108 | struct pci_dev *pdev = to_pci_dev(dev->dev); |
109 | u32 blc_pwm_ctl; |
110 | |
111 | level *= cdv_get_max_backlight(dev); |
112 | level /= 100; |
113 | |
114 | if (cdv_backlight_combination_mode(dev)) { |
115 | u32 max = cdv_get_max_backlight(dev); |
116 | u8 lbpc; |
117 | |
118 | lbpc = level * 0xfe / max + 1; |
119 | level /= lbpc; |
120 | |
121 | pci_write_config_byte(dev: pdev, where: 0xF4, val: lbpc); |
122 | } |
123 | |
124 | blc_pwm_ctl = REG_READ(BLC_PWM_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK; |
125 | REG_WRITE(BLC_PWM_CTL, (blc_pwm_ctl | |
126 | (level << BACKLIGHT_DUTY_CYCLE_SHIFT))); |
127 | } |
128 | |
129 | static int cdv_backlight_init(struct drm_device *dev) |
130 | { |
131 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
132 | |
133 | dev_priv->backlight_level = cdv_get_brightness(dev); |
134 | cdv_set_brightness(dev, level: dev_priv->backlight_level); |
135 | |
136 | return 0; |
137 | } |
138 | |
139 | /* |
140 | * Provide the Cedarview specific chip logic and low level methods |
141 | * for power management |
142 | * |
143 | * FIXME: we need to implement the apm/ospm base management bits |
144 | * for this and the MID devices. |
145 | */ |
146 | |
147 | static inline u32 CDV_MSG_READ32(int domain, uint port, uint offset) |
148 | { |
149 | int mcr = (0x10<<24) | (port << 16) | (offset << 8); |
150 | uint32_t ret_val = 0; |
151 | struct pci_dev *pci_root = pci_get_domain_bus_and_slot(domain, bus: 0, devfn: 0); |
152 | pci_write_config_dword(dev: pci_root, where: 0xD0, val: mcr); |
153 | pci_read_config_dword(dev: pci_root, where: 0xD4, val: &ret_val); |
154 | pci_dev_put(dev: pci_root); |
155 | return ret_val; |
156 | } |
157 | |
158 | static inline void CDV_MSG_WRITE32(int domain, uint port, uint offset, |
159 | u32 value) |
160 | { |
161 | int mcr = (0x11<<24) | (port << 16) | (offset << 8) | 0xF0; |
162 | struct pci_dev *pci_root = pci_get_domain_bus_and_slot(domain, bus: 0, devfn: 0); |
163 | pci_write_config_dword(dev: pci_root, where: 0xD4, val: value); |
164 | pci_write_config_dword(dev: pci_root, where: 0xD0, val: mcr); |
165 | pci_dev_put(dev: pci_root); |
166 | } |
167 | |
168 | #define PSB_PM_SSC 0x20 |
169 | #define PSB_PM_SSS 0x30 |
170 | #define PSB_PWRGT_GFX_ON 0x02 |
171 | #define PSB_PWRGT_GFX_OFF 0x01 |
172 | #define PSB_PWRGT_GFX_D0 0x00 |
173 | #define PSB_PWRGT_GFX_D3 0x03 |
174 | |
175 | static void cdv_init_pm(struct drm_device *dev) |
176 | { |
177 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
178 | struct pci_dev *pdev = to_pci_dev(dev->dev); |
179 | u32 pwr_cnt; |
180 | int domain = pci_domain_nr(bus: pdev->bus); |
181 | int i; |
182 | |
183 | dev_priv->apm_base = CDV_MSG_READ32(domain, PSB_PUNIT_PORT, |
184 | PSB_APMBA) & 0xFFFF; |
185 | dev_priv->ospm_base = CDV_MSG_READ32(domain, PSB_PUNIT_PORT, |
186 | PSB_OSPMBA) & 0xFFFF; |
187 | |
188 | /* Power status */ |
189 | pwr_cnt = inl(port: dev_priv->apm_base + PSB_APM_CMD); |
190 | |
191 | /* Enable the GPU */ |
192 | pwr_cnt &= ~PSB_PWRGT_GFX_MASK; |
193 | pwr_cnt |= PSB_PWRGT_GFX_ON; |
194 | outl(value: pwr_cnt, port: dev_priv->apm_base + PSB_APM_CMD); |
195 | |
196 | /* Wait for the GPU power */ |
197 | for (i = 0; i < 5; i++) { |
198 | u32 pwr_sts = inl(port: dev_priv->apm_base + PSB_APM_STS); |
199 | if ((pwr_sts & PSB_PWRGT_GFX_MASK) == 0) |
200 | return; |
201 | udelay(10); |
202 | } |
203 | dev_err(dev->dev, "GPU: power management timed out.\n" ); |
204 | } |
205 | |
206 | static void cdv_errata(struct drm_device *dev) |
207 | { |
208 | struct pci_dev *pdev = to_pci_dev(dev->dev); |
209 | |
210 | /* Disable bonus launch. |
211 | * CPU and GPU competes for memory and display misses updates and |
212 | * flickers. Worst with dual core, dual displays. |
213 | * |
214 | * Fixes were done to Win 7 gfx driver to disable a feature called |
215 | * Bonus Launch to work around the issue, by degrading |
216 | * performance. |
217 | */ |
218 | CDV_MSG_WRITE32(domain: pci_domain_nr(bus: pdev->bus), port: 3, offset: 0x30, value: 0x08027108); |
219 | } |
220 | |
221 | /** |
222 | * cdv_save_display_registers - save registers lost on suspend |
223 | * @dev: our DRM device |
224 | * |
225 | * Save the state we need in order to be able to restore the interface |
226 | * upon resume from suspend |
227 | */ |
228 | static int cdv_save_display_registers(struct drm_device *dev) |
229 | { |
230 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
231 | struct pci_dev *pdev = to_pci_dev(dev->dev); |
232 | struct psb_save_area *regs = &dev_priv->regs; |
233 | struct drm_connector_list_iter conn_iter; |
234 | struct drm_connector *connector; |
235 | |
236 | dev_dbg(dev->dev, "Saving GPU registers.\n" ); |
237 | |
238 | pci_read_config_byte(dev: pdev, where: 0xF4, val: ®s->cdv.saveLBB); |
239 | |
240 | regs->cdv.saveDSPCLK_GATE_D = REG_READ(DSPCLK_GATE_D); |
241 | regs->cdv.saveRAMCLK_GATE_D = REG_READ(RAMCLK_GATE_D); |
242 | |
243 | regs->cdv.saveDSPARB = REG_READ(DSPARB); |
244 | regs->cdv.saveDSPFW[0] = REG_READ(DSPFW1); |
245 | regs->cdv.saveDSPFW[1] = REG_READ(DSPFW2); |
246 | regs->cdv.saveDSPFW[2] = REG_READ(DSPFW3); |
247 | regs->cdv.saveDSPFW[3] = REG_READ(DSPFW4); |
248 | regs->cdv.saveDSPFW[4] = REG_READ(DSPFW5); |
249 | regs->cdv.saveDSPFW[5] = REG_READ(DSPFW6); |
250 | |
251 | regs->cdv.saveADPA = REG_READ(ADPA); |
252 | |
253 | regs->cdv.savePP_CONTROL = REG_READ(PP_CONTROL); |
254 | regs->cdv.savePFIT_PGM_RATIOS = REG_READ(PFIT_PGM_RATIOS); |
255 | regs->saveBLC_PWM_CTL = REG_READ(BLC_PWM_CTL); |
256 | regs->saveBLC_PWM_CTL2 = REG_READ(BLC_PWM_CTL2); |
257 | regs->cdv.saveLVDS = REG_READ(LVDS); |
258 | |
259 | regs->cdv.savePFIT_CONTROL = REG_READ(PFIT_CONTROL); |
260 | |
261 | regs->cdv.savePP_ON_DELAYS = REG_READ(PP_ON_DELAYS); |
262 | regs->cdv.savePP_OFF_DELAYS = REG_READ(PP_OFF_DELAYS); |
263 | regs->cdv.savePP_CYCLE = REG_READ(PP_CYCLE); |
264 | |
265 | regs->cdv.saveVGACNTRL = REG_READ(VGACNTRL); |
266 | |
267 | regs->cdv.saveIER = REG_READ(PSB_INT_ENABLE_R); |
268 | regs->cdv.saveIMR = REG_READ(PSB_INT_MASK_R); |
269 | |
270 | drm_connector_list_iter_begin(dev, iter: &conn_iter); |
271 | drm_for_each_connector_iter(connector, &conn_iter) |
272 | connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF); |
273 | drm_connector_list_iter_end(iter: &conn_iter); |
274 | |
275 | return 0; |
276 | } |
277 | |
278 | /** |
279 | * cdv_restore_display_registers - restore lost register state |
280 | * @dev: our DRM device |
281 | * |
282 | * Restore register state that was lost during suspend and resume. |
283 | * |
284 | * FIXME: review |
285 | */ |
286 | static int cdv_restore_display_registers(struct drm_device *dev) |
287 | { |
288 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
289 | struct pci_dev *pdev = to_pci_dev(dev->dev); |
290 | struct psb_save_area *regs = &dev_priv->regs; |
291 | struct drm_connector_list_iter conn_iter; |
292 | struct drm_connector *connector; |
293 | u32 temp; |
294 | |
295 | pci_write_config_byte(dev: pdev, where: 0xF4, val: regs->cdv.saveLBB); |
296 | |
297 | REG_WRITE(DSPCLK_GATE_D, regs->cdv.saveDSPCLK_GATE_D); |
298 | REG_WRITE(RAMCLK_GATE_D, regs->cdv.saveRAMCLK_GATE_D); |
299 | |
300 | /* BIOS does below anyway */ |
301 | REG_WRITE(DPIO_CFG, 0); |
302 | REG_WRITE(DPIO_CFG, DPIO_MODE_SELECT_0 | DPIO_CMN_RESET_N); |
303 | |
304 | temp = REG_READ(DPLL_A); |
305 | if ((temp & DPLL_SYNCLOCK_ENABLE) == 0) { |
306 | REG_WRITE(DPLL_A, temp | DPLL_SYNCLOCK_ENABLE); |
307 | REG_READ(DPLL_A); |
308 | } |
309 | |
310 | temp = REG_READ(DPLL_B); |
311 | if ((temp & DPLL_SYNCLOCK_ENABLE) == 0) { |
312 | REG_WRITE(DPLL_B, temp | DPLL_SYNCLOCK_ENABLE); |
313 | REG_READ(DPLL_B); |
314 | } |
315 | |
316 | udelay(500); |
317 | |
318 | REG_WRITE(DSPFW1, regs->cdv.saveDSPFW[0]); |
319 | REG_WRITE(DSPFW2, regs->cdv.saveDSPFW[1]); |
320 | REG_WRITE(DSPFW3, regs->cdv.saveDSPFW[2]); |
321 | REG_WRITE(DSPFW4, regs->cdv.saveDSPFW[3]); |
322 | REG_WRITE(DSPFW5, regs->cdv.saveDSPFW[4]); |
323 | REG_WRITE(DSPFW6, regs->cdv.saveDSPFW[5]); |
324 | |
325 | REG_WRITE(DSPARB, regs->cdv.saveDSPARB); |
326 | REG_WRITE(ADPA, regs->cdv.saveADPA); |
327 | |
328 | REG_WRITE(BLC_PWM_CTL2, regs->saveBLC_PWM_CTL2); |
329 | REG_WRITE(LVDS, regs->cdv.saveLVDS); |
330 | REG_WRITE(PFIT_CONTROL, regs->cdv.savePFIT_CONTROL); |
331 | REG_WRITE(PFIT_PGM_RATIOS, regs->cdv.savePFIT_PGM_RATIOS); |
332 | REG_WRITE(BLC_PWM_CTL, regs->saveBLC_PWM_CTL); |
333 | REG_WRITE(PP_ON_DELAYS, regs->cdv.savePP_ON_DELAYS); |
334 | REG_WRITE(PP_OFF_DELAYS, regs->cdv.savePP_OFF_DELAYS); |
335 | REG_WRITE(PP_CYCLE, regs->cdv.savePP_CYCLE); |
336 | REG_WRITE(PP_CONTROL, regs->cdv.savePP_CONTROL); |
337 | |
338 | REG_WRITE(VGACNTRL, regs->cdv.saveVGACNTRL); |
339 | |
340 | REG_WRITE(PSB_INT_ENABLE_R, regs->cdv.saveIER); |
341 | REG_WRITE(PSB_INT_MASK_R, regs->cdv.saveIMR); |
342 | |
343 | /* Fix arbitration bug */ |
344 | cdv_errata(dev); |
345 | |
346 | drm_mode_config_reset(dev); |
347 | |
348 | drm_connector_list_iter_begin(dev, iter: &conn_iter); |
349 | drm_for_each_connector_iter(connector, &conn_iter) |
350 | connector->funcs->dpms(connector, DRM_MODE_DPMS_ON); |
351 | drm_connector_list_iter_end(iter: &conn_iter); |
352 | |
353 | /* Resume the modeset for every activated CRTC */ |
354 | drm_helper_resume_force_mode(dev); |
355 | return 0; |
356 | } |
357 | |
358 | static int cdv_power_down(struct drm_device *dev) |
359 | { |
360 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
361 | u32 pwr_cnt, pwr_mask, pwr_sts; |
362 | int tries = 5; |
363 | |
364 | pwr_cnt = inl(port: dev_priv->apm_base + PSB_APM_CMD); |
365 | pwr_cnt &= ~PSB_PWRGT_GFX_MASK; |
366 | pwr_cnt |= PSB_PWRGT_GFX_OFF; |
367 | pwr_mask = PSB_PWRGT_GFX_MASK; |
368 | |
369 | outl(value: pwr_cnt, port: dev_priv->apm_base + PSB_APM_CMD); |
370 | |
371 | while (tries--) { |
372 | pwr_sts = inl(port: dev_priv->apm_base + PSB_APM_STS); |
373 | if ((pwr_sts & pwr_mask) == PSB_PWRGT_GFX_D3) |
374 | return 0; |
375 | udelay(10); |
376 | } |
377 | return 0; |
378 | } |
379 | |
380 | static int cdv_power_up(struct drm_device *dev) |
381 | { |
382 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
383 | u32 pwr_cnt, pwr_mask, pwr_sts; |
384 | int tries = 5; |
385 | |
386 | pwr_cnt = inl(port: dev_priv->apm_base + PSB_APM_CMD); |
387 | pwr_cnt &= ~PSB_PWRGT_GFX_MASK; |
388 | pwr_cnt |= PSB_PWRGT_GFX_ON; |
389 | pwr_mask = PSB_PWRGT_GFX_MASK; |
390 | |
391 | outl(value: pwr_cnt, port: dev_priv->apm_base + PSB_APM_CMD); |
392 | |
393 | while (tries--) { |
394 | pwr_sts = inl(port: dev_priv->apm_base + PSB_APM_STS); |
395 | if ((pwr_sts & pwr_mask) == PSB_PWRGT_GFX_D0) |
396 | return 0; |
397 | udelay(10); |
398 | } |
399 | return 0; |
400 | } |
401 | |
402 | static void cdv_hotplug_work_func(struct work_struct *work) |
403 | { |
404 | struct drm_psb_private *dev_priv = container_of(work, struct drm_psb_private, |
405 | hotplug_work); |
406 | struct drm_device *dev = &dev_priv->dev; |
407 | |
408 | /* Just fire off a uevent and let userspace tell us what to do */ |
409 | drm_helper_hpd_irq_event(dev); |
410 | } |
411 | |
412 | /* The core driver has received a hotplug IRQ. We are in IRQ context |
413 | so extract the needed information and kick off queued processing */ |
414 | |
415 | static int cdv_hotplug_event(struct drm_device *dev) |
416 | { |
417 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
418 | schedule_work(work: &dev_priv->hotplug_work); |
419 | REG_WRITE(PORT_HOTPLUG_STAT, REG_READ(PORT_HOTPLUG_STAT)); |
420 | return 1; |
421 | } |
422 | |
423 | static void cdv_hotplug_enable(struct drm_device *dev, bool on) |
424 | { |
425 | if (on) { |
426 | u32 hotplug = REG_READ(PORT_HOTPLUG_EN); |
427 | hotplug |= HDMIB_HOTPLUG_INT_EN | HDMIC_HOTPLUG_INT_EN | |
428 | HDMID_HOTPLUG_INT_EN | CRT_HOTPLUG_INT_EN; |
429 | REG_WRITE(PORT_HOTPLUG_EN, hotplug); |
430 | } else { |
431 | REG_WRITE(PORT_HOTPLUG_EN, 0); |
432 | REG_WRITE(PORT_HOTPLUG_STAT, REG_READ(PORT_HOTPLUG_STAT)); |
433 | } |
434 | } |
435 | |
436 | static const char *force_audio_names[] = { |
437 | "off" , |
438 | "auto" , |
439 | "on" , |
440 | }; |
441 | |
442 | void cdv_intel_attach_force_audio_property(struct drm_connector *connector) |
443 | { |
444 | struct drm_device *dev = connector->dev; |
445 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
446 | struct drm_property *prop; |
447 | int i; |
448 | |
449 | prop = dev_priv->force_audio_property; |
450 | if (prop == NULL) { |
451 | prop = drm_property_create(dev, DRM_MODE_PROP_ENUM, |
452 | name: "audio" , |
453 | ARRAY_SIZE(force_audio_names)); |
454 | if (prop == NULL) |
455 | return; |
456 | |
457 | for (i = 0; i < ARRAY_SIZE(force_audio_names); i++) |
458 | drm_property_add_enum(property: prop, value: i-1, name: force_audio_names[i]); |
459 | |
460 | dev_priv->force_audio_property = prop; |
461 | } |
462 | drm_object_attach_property(obj: &connector->base, property: prop, init_val: 0); |
463 | } |
464 | |
465 | |
466 | static const char *broadcast_rgb_names[] = { |
467 | "Full" , |
468 | "Limited 16:235" , |
469 | }; |
470 | |
471 | void cdv_intel_attach_broadcast_rgb_property(struct drm_connector *connector) |
472 | { |
473 | struct drm_device *dev = connector->dev; |
474 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
475 | struct drm_property *prop; |
476 | int i; |
477 | |
478 | prop = dev_priv->broadcast_rgb_property; |
479 | if (prop == NULL) { |
480 | prop = drm_property_create(dev, DRM_MODE_PROP_ENUM, |
481 | name: "Broadcast RGB" , |
482 | ARRAY_SIZE(broadcast_rgb_names)); |
483 | if (prop == NULL) |
484 | return; |
485 | |
486 | for (i = 0; i < ARRAY_SIZE(broadcast_rgb_names); i++) |
487 | drm_property_add_enum(property: prop, value: i, name: broadcast_rgb_names[i]); |
488 | |
489 | dev_priv->broadcast_rgb_property = prop; |
490 | } |
491 | |
492 | drm_object_attach_property(obj: &connector->base, property: prop, init_val: 0); |
493 | } |
494 | |
495 | /* Cedarview */ |
496 | static const struct psb_offset cdv_regmap[2] = { |
497 | { |
498 | .fp0 = FPA0, |
499 | .fp1 = FPA1, |
500 | .cntr = DSPACNTR, |
501 | .conf = PIPEACONF, |
502 | .src = PIPEASRC, |
503 | .dpll = DPLL_A, |
504 | .dpll_md = DPLL_A_MD, |
505 | .htotal = HTOTAL_A, |
506 | .hblank = HBLANK_A, |
507 | .hsync = HSYNC_A, |
508 | .vtotal = VTOTAL_A, |
509 | .vblank = VBLANK_A, |
510 | .vsync = VSYNC_A, |
511 | .stride = DSPASTRIDE, |
512 | .size = DSPASIZE, |
513 | .pos = DSPAPOS, |
514 | .base = DSPABASE, |
515 | .surf = DSPASURF, |
516 | .addr = DSPABASE, |
517 | .status = PIPEASTAT, |
518 | .linoff = DSPALINOFF, |
519 | .tileoff = DSPATILEOFF, |
520 | .palette = PALETTE_A, |
521 | }, |
522 | { |
523 | .fp0 = FPB0, |
524 | .fp1 = FPB1, |
525 | .cntr = DSPBCNTR, |
526 | .conf = PIPEBCONF, |
527 | .src = PIPEBSRC, |
528 | .dpll = DPLL_B, |
529 | .dpll_md = DPLL_B_MD, |
530 | .htotal = HTOTAL_B, |
531 | .hblank = HBLANK_B, |
532 | .hsync = HSYNC_B, |
533 | .vtotal = VTOTAL_B, |
534 | .vblank = VBLANK_B, |
535 | .vsync = VSYNC_B, |
536 | .stride = DSPBSTRIDE, |
537 | .size = DSPBSIZE, |
538 | .pos = DSPBPOS, |
539 | .base = DSPBBASE, |
540 | .surf = DSPBSURF, |
541 | .addr = DSPBBASE, |
542 | .status = PIPEBSTAT, |
543 | .linoff = DSPBLINOFF, |
544 | .tileoff = DSPBTILEOFF, |
545 | .palette = PALETTE_B, |
546 | } |
547 | }; |
548 | |
549 | static int cdv_chip_setup(struct drm_device *dev) |
550 | { |
551 | struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
552 | INIT_WORK(&dev_priv->hotplug_work, cdv_hotplug_work_func); |
553 | |
554 | dev_priv->use_msi = true; |
555 | dev_priv->regmap = cdv_regmap; |
556 | gma_get_core_freq(dev); |
557 | psb_intel_opregion_init(dev); |
558 | psb_intel_init_bios(dev); |
559 | cdv_hotplug_enable(dev, on: false); |
560 | return 0; |
561 | } |
562 | |
563 | /* CDV is much like Poulsbo but has MID like SGX offsets and PM */ |
564 | |
565 | const struct psb_ops cdv_chip_ops = { |
566 | .name = "GMA3600/3650" , |
567 | .pipes = 2, |
568 | .crtcs = 2, |
569 | .hdmi_mask = (1 << 0) | (1 << 1), |
570 | .lvds_mask = (1 << 1), |
571 | .sdvo_mask = (1 << 0), |
572 | .cursor_needs_phys = 0, |
573 | .sgx_offset = MRST_SGX_OFFSET, |
574 | .chip_setup = cdv_chip_setup, |
575 | .errata = cdv_errata, |
576 | |
577 | .crtc_helper = &cdv_intel_helper_funcs, |
578 | .clock_funcs = &cdv_clock_funcs, |
579 | |
580 | .output_init = cdv_output_init, |
581 | .hotplug = cdv_hotplug_event, |
582 | .hotplug_enable = cdv_hotplug_enable, |
583 | |
584 | .backlight_init = cdv_backlight_init, |
585 | .backlight_get = cdv_get_brightness, |
586 | .backlight_set = cdv_set_brightness, |
587 | .backlight_name = "psb-bl" , |
588 | |
589 | .init_pm = cdv_init_pm, |
590 | .save_regs = cdv_save_display_registers, |
591 | .restore_regs = cdv_restore_display_registers, |
592 | .save_crtc = gma_crtc_save, |
593 | .restore_crtc = gma_crtc_restore, |
594 | .power_down = cdv_power_down, |
595 | .power_up = cdv_power_up, |
596 | .update_wm = cdv_update_wm, |
597 | .disable_sr = cdv_disable_sr, |
598 | }; |
599 | |