diff options
author | Martin Peres <martin.peres@ensi-bourges.fr> | 2011-11-07 17:38:50 -0500 |
---|---|---|
committer | Ben Skeggs <bskeggs@redhat.com> | 2011-12-21 04:01:44 -0500 |
commit | eeb7a50bddb281d7beecb0ad73c9f1233e9932c2 (patch) | |
tree | 64e74c8fd82a07cc58c56cd3fe7ea492048cbbcf | |
parent | abbd3f8e3bea4b2b0490260e67357067a2dc2039 (diff) |
drm/nv50/pm: introduce hwsq-based memory reclocking
More work needs to be done on supporting the different memory types.
v2 (Ben Skeggs):
- fixed up conflicts from not having pausing patch first
- restructured code somewhat to fit with how all the other code works
- fixed bug where incorrect mpll_ctrl could get set sometimes
- removed stuff that's cargo-culted from the binary driver
- merged nv92+ display disable into hwsq
- fixed incorrect opcode 0x5f magic at end of ucode
Signed-off-by: Martin Peres <martin.peres@ensi-bourges.fr>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_hwsq.h | 98 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nv50_pm.c | 207 |
2 files changed, 243 insertions, 62 deletions
diff --git a/drivers/gpu/drm/nouveau/nouveau_hwsq.h b/drivers/gpu/drm/nouveau/nouveau_hwsq.h new file mode 100644 index 000000000000..d59a3b3ff644 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_hwsq.h | |||
@@ -0,0 +1,98 @@ | |||
1 | /* | ||
2 | * Copyright 2010 Red Hat Inc. | ||
3 | * | ||
4 | * Permission is hereby granted, free of charge, to any person obtaining a | ||
5 | * copy of this software and associated documentation files (the "Software"), | ||
6 | * to deal in the Software without restriction, including without limitation | ||
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
8 | * and/or sell copies of the Software, and to permit persons to whom the | ||
9 | * Software is furnished to do so, subject to the following conditions: | ||
10 | * | ||
11 | * The above copyright notice and this permission notice shall be included in | ||
12 | * all copies or substantial portions of the Software. | ||
13 | * | ||
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | ||
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | ||
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | ||
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
20 | * OTHER DEALINGS IN THE SOFTWARE. | ||
21 | * | ||
22 | * Authors: Ben Skeggs | ||
23 | */ | ||
24 | |||
25 | #ifndef __NOUVEAU_HWSQ_H__ | ||
26 | #define __NOUVEAU_HWSQ_H__ | ||
27 | |||
28 | struct hwsq_ucode { | ||
29 | u8 data[0x200]; | ||
30 | union { | ||
31 | u8 *u08; | ||
32 | u16 *u16; | ||
33 | u32 *u32; | ||
34 | } ptr; | ||
35 | u16 len; | ||
36 | |||
37 | u32 reg; | ||
38 | u32 val; | ||
39 | }; | ||
40 | |||
41 | static inline void | ||
42 | hwsq_init(struct hwsq_ucode *hwsq) | ||
43 | { | ||
44 | hwsq->ptr.u08 = hwsq->data; | ||
45 | hwsq->reg = 0xffffffff; | ||
46 | hwsq->val = 0xffffffff; | ||
47 | } | ||
48 | |||
49 | static inline void | ||
50 | hwsq_fini(struct hwsq_ucode *hwsq) | ||
51 | { | ||
52 | do { | ||
53 | *hwsq->ptr.u08++ = 0x7f; | ||
54 | hwsq->len = hwsq->ptr.u08 - hwsq->data; | ||
55 | } while (hwsq->len & 3); | ||
56 | hwsq->ptr.u08 = hwsq->data; | ||
57 | } | ||
58 | |||
59 | static inline void | ||
60 | hwsq_unkn(struct hwsq_ucode *hwsq, u8 v0) | ||
61 | { | ||
62 | *hwsq->ptr.u08++ = v0; | ||
63 | } | ||
64 | |||
65 | static inline void | ||
66 | hwsq_op5f(struct hwsq_ucode *hwsq, u8 v0, u8 v1) | ||
67 | { | ||
68 | *hwsq->ptr.u08++ = 0x5f; | ||
69 | *hwsq->ptr.u08++ = v0; | ||
70 | *hwsq->ptr.u08++ = v1; | ||
71 | } | ||
72 | |||
73 | static inline void | ||
74 | hwsq_wr32(struct hwsq_ucode *hwsq, u32 reg, u32 val) | ||
75 | { | ||
76 | if (val != hwsq->val) { | ||
77 | if ((val & 0xffff0000) == (hwsq->val & 0xffff0000)) { | ||
78 | *hwsq->ptr.u08++ = 0x42; | ||
79 | *hwsq->ptr.u16++ = (val & 0x0000ffff); | ||
80 | } else { | ||
81 | *hwsq->ptr.u08++ = 0xe2; | ||
82 | *hwsq->ptr.u32++ = val; | ||
83 | } | ||
84 | |||
85 | hwsq->val = val; | ||
86 | } | ||
87 | |||
88 | if ((reg & 0xffff0000) == (hwsq->reg & 0xffff0000)) { | ||
89 | *hwsq->ptr.u08++ = 0x40; | ||
90 | *hwsq->ptr.u16++ = (reg & 0x0000ffff); | ||
91 | } else { | ||
92 | *hwsq->ptr.u08++ = 0xe0; | ||
93 | *hwsq->ptr.u32++ = reg; | ||
94 | } | ||
95 | hwsq->reg = reg; | ||
96 | } | ||
97 | |||
98 | #endif | ||
diff --git a/drivers/gpu/drm/nouveau/nv50_pm.c b/drivers/gpu/drm/nouveau/nv50_pm.c index e025cae5ef23..22789db48969 100644 --- a/drivers/gpu/drm/nouveau/nv50_pm.c +++ b/drivers/gpu/drm/nouveau/nv50_pm.c | |||
@@ -27,6 +27,7 @@ | |||
27 | #include "nouveau_bios.h" | 27 | #include "nouveau_bios.h" |
28 | #include "nouveau_hw.h" | 28 | #include "nouveau_hw.h" |
29 | #include "nouveau_pm.h" | 29 | #include "nouveau_pm.h" |
30 | #include "nouveau_hwsq.h" | ||
30 | 31 | ||
31 | enum clk_src { | 32 | enum clk_src { |
32 | clk_src_crystal, | 33 | clk_src_crystal, |
@@ -351,6 +352,9 @@ nv50_pm_clocks_get(struct drm_device *dev, struct nouveau_pm_level *perflvl) | |||
351 | } | 352 | } |
352 | 353 | ||
353 | struct nv50_pm_state { | 354 | struct nv50_pm_state { |
355 | struct hwsq_ucode mclk_hwsq; | ||
356 | u32 mscript; | ||
357 | |||
354 | u32 emast; | 358 | u32 emast; |
355 | u32 nctrl; | 359 | u32 nctrl; |
356 | u32 ncoef; | 360 | u32 ncoef; |
@@ -359,10 +363,6 @@ struct nv50_pm_state { | |||
359 | 363 | ||
360 | u32 amast; | 364 | u32 amast; |
361 | u32 pdivs; | 365 | u32 pdivs; |
362 | |||
363 | u32 mscript; | ||
364 | u32 mctrl; | ||
365 | u32 mcoef; | ||
366 | }; | 366 | }; |
367 | 367 | ||
368 | static u32 | 368 | static u32 |
@@ -415,6 +415,80 @@ clk_same(u32 a, u32 b) | |||
415 | return ((a / 1000) == (b / 1000)); | 415 | return ((a / 1000) == (b / 1000)); |
416 | } | 416 | } |
417 | 417 | ||
418 | static int | ||
419 | calc_mclk(struct drm_device *dev, u32 freq, struct hwsq_ucode *hwsq) | ||
420 | { | ||
421 | struct drm_nouveau_private *dev_priv = dev->dev_private; | ||
422 | struct pll_lims pll; | ||
423 | u32 mast = nv_rd32(dev, 0x00c040); | ||
424 | u32 ctrl = nv_rd32(dev, 0x004008); | ||
425 | u32 coef = nv_rd32(dev, 0x00400c); | ||
426 | u32 orig = ctrl; | ||
427 | u32 crtc_mask = 0; | ||
428 | int N, M, P; | ||
429 | int ret, i; | ||
430 | |||
431 | /* use pcie refclock if possible, otherwise use mpll */ | ||
432 | ctrl &= ~0x81ff0200; | ||
433 | if (clk_same(freq, read_clk(dev, clk_src_href))) { | ||
434 | ctrl |= 0x00000200 | (pll.log2p_bias << 19); | ||
435 | } else { | ||
436 | ret = calc_pll(dev, 0x4008, &pll, freq, &N, &M, &P); | ||
437 | if (ret == 0) | ||
438 | return -EINVAL; | ||
439 | |||
440 | ctrl |= 0x80000000 | (P << 22) | (P << 16); | ||
441 | ctrl |= pll.log2p_bias << 19; | ||
442 | coef = (N << 8) | M; | ||
443 | } | ||
444 | |||
445 | mast &= ~0xc0000000; /* get MCLK_2 from HREF */ | ||
446 | mast |= 0x0000c000; /* use MCLK_2 as MPLL_BYPASS clock */ | ||
447 | |||
448 | /* determine active crtcs */ | ||
449 | for (i = 0; i < 2; i++) { | ||
450 | if (nv_rd32(dev, NV50_PDISPLAY_CRTC_C(i, CLOCK))) | ||
451 | crtc_mask |= (1 << i); | ||
452 | } | ||
453 | |||
454 | /* build the ucode which will reclock the memory for us */ | ||
455 | hwsq_init(hwsq); | ||
456 | if (crtc_mask) { | ||
457 | hwsq_op5f(hwsq, crtc_mask, 0x00); /* wait for scanout */ | ||
458 | hwsq_op5f(hwsq, crtc_mask, 0x01); /* wait for vblank */ | ||
459 | } | ||
460 | if (dev_priv->chipset >= 0x92) | ||
461 | hwsq_wr32(hwsq, 0x611200, 0x00003300); /* disable scanout */ | ||
462 | hwsq_unkn(hwsq, 0xb0); /* disable bus access */ | ||
463 | hwsq_op5f(hwsq, 0x00, 0x01); /* no idea :s */ | ||
464 | |||
465 | /* prepare memory controller */ | ||
466 | hwsq_wr32(hwsq, 0x1002d4, 0x00000001); /* precharge banks and idle */ | ||
467 | hwsq_wr32(hwsq, 0x1002d0, 0x00000001); /* force refresh */ | ||
468 | hwsq_wr32(hwsq, 0x100210, 0x00000000); /* stop the automatic refresh */ | ||
469 | hwsq_wr32(hwsq, 0x1002dc, 0x00000001); /* start self refresh mode */ | ||
470 | |||
471 | /* reclock memory */ | ||
472 | hwsq_wr32(hwsq, 0xc040, mast); | ||
473 | hwsq_wr32(hwsq, 0x4008, orig | 0x00000200); /* bypass MPLL */ | ||
474 | hwsq_wr32(hwsq, 0x400c, coef); | ||
475 | hwsq_wr32(hwsq, 0x4008, ctrl); | ||
476 | |||
477 | /* restart memory controller */ | ||
478 | hwsq_wr32(hwsq, 0x1002d4, 0x00000001); /* precharge banks and idle */ | ||
479 | hwsq_wr32(hwsq, 0x1002dc, 0x00000000); /* stop self refresh mode */ | ||
480 | hwsq_wr32(hwsq, 0x100210, 0x80000000); /* restart automatic refresh */ | ||
481 | hwsq_unkn(hwsq, 0x07); /* wait for the PLL to stabilize (12us) */ | ||
482 | |||
483 | hwsq_unkn(hwsq, 0x0b); /* may be unnecessary: causes flickering */ | ||
484 | hwsq_unkn(hwsq, 0xd0); /* enable bus access again */ | ||
485 | hwsq_op5f(hwsq, 0x00, 0x00); /* no idea, reverse of 0x00, 0x01? */ | ||
486 | if (dev_priv->chipset >= 0x92) | ||
487 | hwsq_wr32(hwsq, 0x611200, 0x00003330); /* enable scanout */ | ||
488 | hwsq_fini(hwsq); | ||
489 | return 0; | ||
490 | } | ||
491 | |||
418 | void * | 492 | void * |
419 | nv50_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl) | 493 | nv50_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl) |
420 | { | 494 | { |
@@ -462,23 +536,16 @@ nv50_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl) | |||
462 | info->scoef = (N << 8) | M; | 536 | info->scoef = (N << 8) | M; |
463 | } | 537 | } |
464 | 538 | ||
465 | /* memory: use pcie refclock if possible, otherwise use mpll */ | 539 | /* memory: build hwsq ucode which we'll use to reclock memory */ |
466 | info->mscript = perflvl->memscript; | 540 | info->mclk_hwsq.len = 0; |
467 | if (clk_same(perflvl->memory, read_clk(dev, clk_src_href))) { | ||
468 | info->mctrl = 0x00000200 | (pll.log2p_bias << 19); | ||
469 | info->mcoef = nv_rd32(dev, 0x400c); | ||
470 | } else | ||
471 | if (perflvl->memory) { | 541 | if (perflvl->memory) { |
472 | clk = calc_pll(dev, 0x4008, &pll, perflvl->memory, | 542 | clk = calc_mclk(dev, perflvl->memory, &info->mclk_hwsq); |
473 | &N, &M, &P1); | 543 | if (clk < 0) { |
474 | if (clk == 0) | 544 | ret = clk; |
475 | goto error; | 545 | goto error; |
546 | } | ||
476 | 547 | ||
477 | info->mctrl = 0x80000000 | (P1 << 22) | (P1 << 16); | 548 | info->mscript = perflvl->memscript; |
478 | info->mctrl |= pll.log2p_bias << 19; | ||
479 | info->mcoef = (N << 8) | M; | ||
480 | } else { | ||
481 | info->mctrl = 0x00000000; | ||
482 | } | 549 | } |
483 | 550 | ||
484 | /* vdec: avoid modifying xpll until we know exactly how the other | 551 | /* vdec: avoid modifying xpll until we know exactly how the other |
@@ -537,6 +604,44 @@ error: | |||
537 | return ERR_PTR(ret); | 604 | return ERR_PTR(ret); |
538 | } | 605 | } |
539 | 606 | ||
607 | static int | ||
608 | prog_mclk(struct drm_device *dev, struct hwsq_ucode *hwsq) | ||
609 | { | ||
610 | struct drm_nouveau_private *dev_priv = dev->dev_private; | ||
611 | u32 hwsq_data, hwsq_kick; | ||
612 | int i; | ||
613 | |||
614 | if (dev_priv->chipset < 0x90) { | ||
615 | hwsq_data = 0x001400; | ||
616 | hwsq_kick = 0x00000003; | ||
617 | } else { | ||
618 | hwsq_data = 0x080000; | ||
619 | hwsq_kick = 0x00000001; | ||
620 | } | ||
621 | |||
622 | /* upload hwsq ucode */ | ||
623 | nv_mask(dev, 0x001098, 0x00000008, 0x00000000); | ||
624 | nv_wr32(dev, 0x001304, 0x00000000); | ||
625 | for (i = 0; i < hwsq->len / 4; i++) | ||
626 | nv_wr32(dev, hwsq_data + (i * 4), hwsq->ptr.u32[i]); | ||
627 | nv_mask(dev, 0x001098, 0x00000018, 0x00000018); | ||
628 | |||
629 | /* launch, and wait for completion */ | ||
630 | nv_wr32(dev, 0x00130c, hwsq_kick); | ||
631 | if (!nv_wait(dev, 0x001308, 0x00000100, 0x00000000)) { | ||
632 | NV_ERROR(dev, "hwsq ucode exec timed out\n"); | ||
633 | NV_ERROR(dev, "0x001308: 0x%08x\n", nv_rd32(dev, 0x001308)); | ||
634 | for (i = 0; i < hwsq->len / 4; i++) { | ||
635 | NV_ERROR(dev, "0x%06x: 0x%08x\n", 0x1400 + (i * 4), | ||
636 | nv_rd32(dev, 0x001400 + (i * 4))); | ||
637 | } | ||
638 | |||
639 | return -EIO; | ||
640 | } | ||
641 | |||
642 | return 0; | ||
643 | } | ||
644 | |||
540 | int | 645 | int |
541 | nv50_pm_clocks_set(struct drm_device *dev, void *data) | 646 | nv50_pm_clocks_set(struct drm_device *dev, void *data) |
542 | { | 647 | { |
@@ -550,6 +655,28 @@ nv50_pm_clocks_set(struct drm_device *dev, void *data) | |||
550 | if (!nv_wait(dev, 0x002504, 0x00000010, 0x00000010)) | 655 | if (!nv_wait(dev, 0x002504, 0x00000010, 0x00000010)) |
551 | goto error; | 656 | goto error; |
552 | 657 | ||
658 | /* memory: it is *very* important we change this first, the ucode | ||
659 | * we build in pre() now has hardcoded 0xc040 values, which can't | ||
660 | * change before we execute it or the engine clocks may end up | ||
661 | * messed up. | ||
662 | */ | ||
663 | if (info->mclk_hwsq.len) { | ||
664 | /* execute some scripts that do ??? from the vbios.. */ | ||
665 | if (!bit_table(dev, 'M', &M) && M.version == 1) { | ||
666 | if (M.length >= 6) | ||
667 | nouveau_bios_init_exec(dev, ROM16(M.data[5])); | ||
668 | if (M.length >= 8) | ||
669 | nouveau_bios_init_exec(dev, ROM16(M.data[7])); | ||
670 | if (M.length >= 10) | ||
671 | nouveau_bios_init_exec(dev, ROM16(M.data[9])); | ||
672 | nouveau_bios_init_exec(dev, info->mscript); | ||
673 | } | ||
674 | |||
675 | ret = prog_mclk(dev, &info->mclk_hwsq); | ||
676 | if (ret) | ||
677 | goto resume; | ||
678 | } | ||
679 | |||
553 | /* reclock vdec/dom6 */ | 680 | /* reclock vdec/dom6 */ |
554 | nv_mask(dev, 0x00c040, 0x00000c00, 0x00000000); | 681 | nv_mask(dev, 0x00c040, 0x00000c00, 0x00000000); |
555 | switch (dev_priv->chipset) { | 682 | switch (dev_priv->chipset) { |
@@ -578,50 +705,6 @@ nv50_pm_clocks_set(struct drm_device *dev, void *data) | |||
578 | nv_wr32(dev, 0x00402c, info->ncoef); | 705 | nv_wr32(dev, 0x00402c, info->ncoef); |
579 | nv_mask(dev, 0x00c040, 0x00100033, info->emast); | 706 | nv_mask(dev, 0x00c040, 0x00100033, info->emast); |
580 | 707 | ||
581 | /* memory */ | ||
582 | if (!info->mctrl) | ||
583 | goto resume; | ||
584 | |||
585 | /* execute some scripts that do ??? from the vbios.. */ | ||
586 | if (!bit_table(dev, 'M', &M) && M.version == 1) { | ||
587 | if (M.length >= 6) | ||
588 | nouveau_bios_init_exec(dev, ROM16(M.data[5])); | ||
589 | if (M.length >= 8) | ||
590 | nouveau_bios_init_exec(dev, ROM16(M.data[7])); | ||
591 | if (M.length >= 10) | ||
592 | nouveau_bios_init_exec(dev, ROM16(M.data[9])); | ||
593 | nouveau_bios_init_exec(dev, info->mscript); | ||
594 | } | ||
595 | |||
596 | /* disable display */ | ||
597 | if (dev_priv->chipset >= 0x92) { | ||
598 | nv_wr32(dev, 0x611200, 0x00003300); | ||
599 | udelay(100); | ||
600 | } | ||
601 | |||
602 | /* prepare ram for reclocking */ | ||
603 | nv_wr32(dev, 0x1002d4, 0x00000001); /* precharge */ | ||
604 | nv_wr32(dev, 0x1002d0, 0x00000001); /* refresh */ | ||
605 | nv_wr32(dev, 0x1002d0, 0x00000001); /* refresh */ | ||
606 | nv_mask(dev, 0x100210, 0x80000000, 0x00000000); /* no auto-refresh */ | ||
607 | nv_wr32(dev, 0x1002dc, 0x00000001); /* enable self-refresh */ | ||
608 | |||
609 | /* modify mpll */ | ||
610 | nv_mask(dev, 0x00c040, 0x0000c000, 0x0000c000); | ||
611 | nv_mask(dev, 0x004008, 0x01ff0200, 0x00000200 | info->mctrl); | ||
612 | nv_wr32(dev, 0x00400c, info->mcoef); | ||
613 | udelay(100); | ||
614 | nv_mask(dev, 0x004008, 0x81ff0200, info->mctrl); | ||
615 | |||
616 | /* re-enable normal operation of memory controller */ | ||
617 | nv_wr32(dev, 0x1002dc, 0x00000000); | ||
618 | nv_mask(dev, 0x100210, 0x80000000, 0x80000000); | ||
619 | udelay(100); | ||
620 | |||
621 | /* re-enable display */ | ||
622 | if (dev_priv->chipset >= 0x92) | ||
623 | nv_wr32(dev, 0x611200, 0x00003330); | ||
624 | |||
625 | goto resume; | 708 | goto resume; |
626 | error: | 709 | error: |
627 | ret = -EBUSY; | 710 | ret = -EBUSY; |