diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/video/fbdev/matrox | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function,
a crash can be triggered.
The eBPF tool probes offset 257 of the __hrtimer_run_queues()
function:
<__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1)
<__hrtimer_run_queues+254>: mov %r14,%rdi
<__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12>
<__hrtimer_run_queues+263>: mov %eax,%r12d
<__hrtimer_run_queues+266>: xchg %ax,%ax
<__hrtimer_run_queues+268>: mov %r13,%rdi
Which triggers this crash:
BUG: unable to handle page fault for address: 00000000000f41c9
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: 0002 [#1] SMP NOPTI
CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P
RIP: 0010:__hrtimer_run_queues+0x106/0x230
Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is
at the 6th byte of the above CS CALL instruction. Since the CS CALL
instruction occupies 6 bytes, the exception occurred in the middle of that
call instruction.
The root cause is that when using eBPF tools to probe in the middle of a
function, a kprobe with INT3 is used as the underlying implementation.
During single-step emulation of the original CALL instruction,
int3_emulate_call() assumes that the probed CALL instruction is 5 bytes
long. However, the actual CS-prefixed CALL instruction occupies 6 bytes,
so it constructs an incorrect exception return address. When the CPU
returns from the kprobe handler, the next instruction to be executed is at
the address of the last byte of that CS CALL instruction. Coincidentally,
starting from that address, the CPU fetches and decodes a completely
different instruction, which ultimately triggers a kernel crash.
Fix the issue by using the actual instruction length obtained from
the instruction decoder when constructing the exception return
address, rather than relying on the hardcoded CALL_INSN_SIZE macro.
[ mingo: Refined the changelog ]
Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step")
Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/video/fbdev/matrox')
20 files changed, 10270 insertions, 0 deletions
diff --git a/drivers/video/fbdev/matrox/Makefile b/drivers/video/fbdev/matrox/Makefile new file mode 100644 index 000000000..f39088e5d --- /dev/null +++ b/drivers/video/fbdev/matrox/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for the Linux video drivers. +# 5 Aug 1999, James Simmons, <mailto:jsimmons@edgeglobal.com> +# Rewritten to use lists instead of if-statements. + +# Each configuration option enables a list of files. + +my-obj-$(CONFIG_FB_MATROX_G) += g450_pll.o matroxfb_g450.o matroxfb_crtc2.o + +obj-$(CONFIG_FB_MATROX) += matroxfb_base.o matroxfb_accel.o matroxfb_DAC1064.o matroxfb_Ti3026.o matroxfb_misc.o $(my-obj-y) +obj-$(CONFIG_FB_MATROX_I2C) += i2c-matroxfb.o +obj-$(CONFIG_FB_MATROX_MAVEN) += matroxfb_maven.o matroxfb_crtc2.o diff --git a/drivers/video/fbdev/matrox/g450_pll.c b/drivers/video/fbdev/matrox/g450_pll.c new file mode 100644 index 000000000..6a08f78cd --- /dev/null +++ b/drivers/video/fbdev/matrox/g450_pll.c @@ -0,0 +1,519 @@ +/* + * + * Hardware accelerated Matrox PCI cards - G450/G550 PLL control. + * + * (c) 2001-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.64 2002/06/10 + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ + +#include <linux/export.h> + +#include "g450_pll.h" +#include "matroxfb_DAC1064.h" + +static inline unsigned int g450_vco2f(unsigned char p, unsigned int fvco) { + return (p & 0x40) ? fvco : fvco >> ((p & 3) + 1); +} + +static inline unsigned int g450_f2vco(unsigned char p, unsigned int fin) { + return (p & 0x40) ? fin : fin << ((p & 3) + 1); +} + +static unsigned int g450_mnp2vco(const struct matrox_fb_info *minfo, + unsigned int mnp) +{ + unsigned int m, n; + + m = ((mnp >> 16) & 0x0FF) + 1; + n = ((mnp >> 7) & 0x1FE) + 4; + return (minfo->features.pll.ref_freq * n + (m >> 1)) / m; +} + +unsigned int g450_mnp2f(const struct matrox_fb_info *minfo, unsigned int mnp) +{ + return g450_vco2f(mnp, g450_mnp2vco(minfo, mnp)); +} + +static inline unsigned int pll_freq_delta(unsigned int f1, unsigned int f2) { + if (f2 < f1) { + f2 = f1 - f2; + } else { + f2 = f2 - f1; + } + return f2; +} + +#define NO_MORE_MNP 0x01FFFFFF +#define G450_MNP_FREQBITS (0xFFFFFF43) /* do not mask high byte so we'll catch NO_MORE_MNP */ + +static unsigned int g450_nextpll(const struct matrox_fb_info *minfo, + const struct matrox_pll_limits *pi, + unsigned int *fvco, unsigned int mnp) +{ + unsigned int m, n, p; + unsigned int tvco = *fvco; + + m = (mnp >> 16) & 0xFF; + p = mnp & 0xFF; + + do { + if (m == 0 || m == 0xFF) { + if (m == 0) { + if (p & 0x40) { + return NO_MORE_MNP; + } + if (p & 3) { + p--; + } else { + p = 0x40; + } + tvco >>= 1; + if (tvco < pi->vcomin) { + return NO_MORE_MNP; + } + *fvco = tvco; + } + + p &= 0x43; + if (tvco < 550000) { +/* p |= 0x00; */ + } else if (tvco < 700000) { + p |= 0x08; + } else if (tvco < 1000000) { + p |= 0x10; + } else if (tvco < 1150000) { + p |= 0x18; + } else { + p |= 0x20; + } + m = 9; + } else { + m--; + } + n = ((tvco * (m+1) + minfo->features.pll.ref_freq) / (minfo->features.pll.ref_freq * 2)) - 2; + } while (n < 0x03 || n > 0x7A); + return (m << 16) | (n << 8) | p; +} + +static unsigned int g450_firstpll(const struct matrox_fb_info *minfo, + const struct matrox_pll_limits *pi, + unsigned int *vco, unsigned int fout) +{ + unsigned int p; + unsigned int vcomax; + + vcomax = pi->vcomax; + if (fout > (vcomax / 2)) { + if (fout > vcomax) { + *vco = vcomax; + } else { + *vco = fout; + } + p = 0x40; + } else { + unsigned int tvco; + + p = 3; + tvco = g450_f2vco(p, fout); + while (p && (tvco > vcomax)) { + p--; + tvco >>= 1; + } + if (tvco < pi->vcomin) { + tvco = pi->vcomin; + } + *vco = tvco; + } + return g450_nextpll(minfo, pi, vco, 0xFF0000 | p); +} + +static inline unsigned int g450_setpll(const struct matrox_fb_info *minfo, + unsigned int mnp, unsigned int pll) +{ + switch (pll) { + case M_PIXEL_PLL_A: + matroxfb_DAC_out(minfo, M1064_XPIXPLLAM, mnp >> 16); + matroxfb_DAC_out(minfo, M1064_XPIXPLLAN, mnp >> 8); + matroxfb_DAC_out(minfo, M1064_XPIXPLLAP, mnp); + return M1064_XPIXPLLSTAT; + + case M_PIXEL_PLL_B: + matroxfb_DAC_out(minfo, M1064_XPIXPLLBM, mnp >> 16); + matroxfb_DAC_out(minfo, M1064_XPIXPLLBN, mnp >> 8); + matroxfb_DAC_out(minfo, M1064_XPIXPLLBP, mnp); + return M1064_XPIXPLLSTAT; + + case M_PIXEL_PLL_C: + matroxfb_DAC_out(minfo, M1064_XPIXPLLCM, mnp >> 16); + matroxfb_DAC_out(minfo, M1064_XPIXPLLCN, mnp >> 8); + matroxfb_DAC_out(minfo, M1064_XPIXPLLCP, mnp); + return M1064_XPIXPLLSTAT; + + case M_SYSTEM_PLL: + matroxfb_DAC_out(minfo, DAC1064_XSYSPLLM, mnp >> 16); + matroxfb_DAC_out(minfo, DAC1064_XSYSPLLN, mnp >> 8); + matroxfb_DAC_out(minfo, DAC1064_XSYSPLLP, mnp); + return DAC1064_XSYSPLLSTAT; + + case M_VIDEO_PLL: + matroxfb_DAC_out(minfo, M1064_XVIDPLLM, mnp >> 16); + matroxfb_DAC_out(minfo, M1064_XVIDPLLN, mnp >> 8); + matroxfb_DAC_out(minfo, M1064_XVIDPLLP, mnp); + return M1064_XVIDPLLSTAT; + } + return 0; +} + +static inline unsigned int g450_cmppll(const struct matrox_fb_info *minfo, + unsigned int mnp, unsigned int pll) +{ + unsigned char m = mnp >> 16; + unsigned char n = mnp >> 8; + unsigned char p = mnp; + + switch (pll) { + case M_PIXEL_PLL_A: + return (matroxfb_DAC_in(minfo, M1064_XPIXPLLAM) != m || + matroxfb_DAC_in(minfo, M1064_XPIXPLLAN) != n || + matroxfb_DAC_in(minfo, M1064_XPIXPLLAP) != p); + + case M_PIXEL_PLL_B: + return (matroxfb_DAC_in(minfo, M1064_XPIXPLLBM) != m || + matroxfb_DAC_in(minfo, M1064_XPIXPLLBN) != n || + matroxfb_DAC_in(minfo, M1064_XPIXPLLBP) != p); + + case M_PIXEL_PLL_C: + return (matroxfb_DAC_in(minfo, M1064_XPIXPLLCM) != m || + matroxfb_DAC_in(minfo, M1064_XPIXPLLCN) != n || + matroxfb_DAC_in(minfo, M1064_XPIXPLLCP) != p); + + case M_SYSTEM_PLL: + return (matroxfb_DAC_in(minfo, DAC1064_XSYSPLLM) != m || + matroxfb_DAC_in(minfo, DAC1064_XSYSPLLN) != n || + matroxfb_DAC_in(minfo, DAC1064_XSYSPLLP) != p); + + case M_VIDEO_PLL: + return (matroxfb_DAC_in(minfo, M1064_XVIDPLLM) != m || + matroxfb_DAC_in(minfo, M1064_XVIDPLLN) != n || + matroxfb_DAC_in(minfo, M1064_XVIDPLLP) != p); + } + return 1; +} + +static inline int g450_isplllocked(const struct matrox_fb_info *minfo, + unsigned int regidx) +{ + unsigned int j; + + for (j = 0; j < 1000; j++) { + if (matroxfb_DAC_in(minfo, regidx) & 0x40) { + unsigned int r = 0; + int i; + + for (i = 0; i < 100; i++) { + r += matroxfb_DAC_in(minfo, regidx) & 0x40; + } + return r >= (90 * 0x40); + } + /* udelay(1)... but DAC_in is much slower... */ + } + return 0; +} + +static int g450_testpll(const struct matrox_fb_info *minfo, unsigned int mnp, + unsigned int pll) +{ + return g450_isplllocked(minfo, g450_setpll(minfo, mnp, pll)); +} + +static void updatehwstate_clk(struct matrox_hw_state* hw, unsigned int mnp, unsigned int pll) { + switch (pll) { + case M_SYSTEM_PLL: + hw->DACclk[3] = mnp >> 16; + hw->DACclk[4] = mnp >> 8; + hw->DACclk[5] = mnp; + break; + } +} + +void matroxfb_g450_setpll_cond(struct matrox_fb_info *minfo, unsigned int mnp, + unsigned int pll) +{ + if (g450_cmppll(minfo, mnp, pll)) { + g450_setpll(minfo, mnp, pll); + } +} + +static inline unsigned int g450_findworkingpll(struct matrox_fb_info *minfo, + unsigned int pll, + unsigned int *mnparray, + unsigned int mnpcount) +{ + unsigned int found = 0; + unsigned int idx; + unsigned int mnpfound = mnparray[0]; + + for (idx = 0; idx < mnpcount; idx++) { + unsigned int sarray[3]; + unsigned int *sptr; + { + unsigned int mnp; + + sptr = sarray; + mnp = mnparray[idx]; + if (mnp & 0x38) { + *sptr++ = mnp - 8; + } + if ((mnp & 0x38) != 0x38) { + *sptr++ = mnp + 8; + } + *sptr = mnp; + } + while (sptr >= sarray) { + unsigned int mnp = *sptr--; + + if (g450_testpll(minfo, mnp - 0x0300, pll) && + g450_testpll(minfo, mnp + 0x0300, pll) && + g450_testpll(minfo, mnp - 0x0200, pll) && + g450_testpll(minfo, mnp + 0x0200, pll) && + g450_testpll(minfo, mnp - 0x0100, pll) && + g450_testpll(minfo, mnp + 0x0100, pll)) { + if (g450_testpll(minfo, mnp, pll)) { + return mnp; + } + } else if (!found && g450_testpll(minfo, mnp, pll)) { + mnpfound = mnp; + found = 1; + } + } + } + g450_setpll(minfo, mnpfound, pll); + return mnpfound; +} + +static void g450_addcache(struct matrox_pll_cache* ci, unsigned int mnp_key, unsigned int mnp_value) { + if (++ci->valid > ARRAY_SIZE(ci->data)) { + ci->valid = ARRAY_SIZE(ci->data); + } + memmove(ci->data + 1, ci->data, (ci->valid - 1) * sizeof(*ci->data)); + ci->data[0].mnp_key = mnp_key & G450_MNP_FREQBITS; + ci->data[0].mnp_value = mnp_value; +} + +static int g450_checkcache(struct matrox_fb_info *minfo, + struct matrox_pll_cache *ci, unsigned int mnp_key) +{ + unsigned int i; + + mnp_key &= G450_MNP_FREQBITS; + for (i = 0; i < ci->valid; i++) { + if (ci->data[i].mnp_key == mnp_key) { + unsigned int mnp; + + mnp = ci->data[i].mnp_value; + if (i) { + memmove(ci->data + 1, ci->data, i * sizeof(*ci->data)); + ci->data[0].mnp_key = mnp_key; + ci->data[0].mnp_value = mnp; + } + return mnp; + } + } + return NO_MORE_MNP; +} + +static int __g450_setclk(struct matrox_fb_info *minfo, unsigned int fout, + unsigned int pll, unsigned int *mnparray, + unsigned int *deltaarray) +{ + unsigned int mnpcount; + const struct matrox_pll_limits* pi; + struct matrox_pll_cache* ci; + + switch (pll) { + case M_PIXEL_PLL_A: + case M_PIXEL_PLL_B: + case M_PIXEL_PLL_C: + { + u_int8_t tmp, xpwrctrl; + unsigned long flags; + + matroxfb_DAC_lock_irqsave(flags); + + xpwrctrl = matroxfb_DAC_in(minfo, M1064_XPWRCTRL); + matroxfb_DAC_out(minfo, M1064_XPWRCTRL, xpwrctrl & ~M1064_XPWRCTRL_PANELPDN); + mga_outb(M_SEQ_INDEX, M_SEQ1); + mga_outb(M_SEQ_DATA, mga_inb(M_SEQ_DATA) | M_SEQ1_SCROFF); + tmp = matroxfb_DAC_in(minfo, M1064_XPIXCLKCTRL); + tmp |= M1064_XPIXCLKCTRL_DIS; + if (!(tmp & M1064_XPIXCLKCTRL_PLL_UP)) { + tmp |= M1064_XPIXCLKCTRL_PLL_UP; + } + matroxfb_DAC_out(minfo, M1064_XPIXCLKCTRL, tmp); + /* DVI PLL preferred for frequencies up to + panel link max, standard PLL otherwise */ + if (fout >= minfo->max_pixel_clock_panellink) + tmp = 0; + else tmp = + M1064_XDVICLKCTRL_DVIDATAPATHSEL | + M1064_XDVICLKCTRL_C1DVICLKSEL | + M1064_XDVICLKCTRL_C1DVICLKEN | + M1064_XDVICLKCTRL_DVILOOPCTL | + M1064_XDVICLKCTRL_P1LOOPBWDTCTL; + /* Setting this breaks PC systems so don't do it */ + /* matroxfb_DAC_out(minfo, M1064_XDVICLKCTRL, tmp); */ + matroxfb_DAC_out(minfo, M1064_XPWRCTRL, + xpwrctrl); + + matroxfb_DAC_unlock_irqrestore(flags); + } + { + u_int8_t misc; + + misc = mga_inb(M_MISC_REG_READ) & ~0x0C; + switch (pll) { + case M_PIXEL_PLL_A: + break; + case M_PIXEL_PLL_B: + misc |= 0x04; + break; + default: + misc |= 0x0C; + break; + } + mga_outb(M_MISC_REG, misc); + } + pi = &minfo->limits.pixel; + ci = &minfo->cache.pixel; + break; + case M_SYSTEM_PLL: + { + u_int32_t opt; + + pci_read_config_dword(minfo->pcidev, PCI_OPTION_REG, &opt); + if (!(opt & 0x20)) { + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, opt | 0x20); + } + } + pi = &minfo->limits.system; + ci = &minfo->cache.system; + break; + case M_VIDEO_PLL: + { + u_int8_t tmp; + unsigned int mnp __maybe_unused; + unsigned long flags; + + matroxfb_DAC_lock_irqsave(flags); + tmp = matroxfb_DAC_in(minfo, M1064_XPWRCTRL); + if (!(tmp & 2)) { + matroxfb_DAC_out(minfo, M1064_XPWRCTRL, tmp | 2); + } + + mnp = matroxfb_DAC_in(minfo, M1064_XPIXPLLCM) << 16; + mnp |= matroxfb_DAC_in(minfo, M1064_XPIXPLLCN) << 8; + matroxfb_DAC_unlock_irqrestore(flags); + } + pi = &minfo->limits.video; + ci = &minfo->cache.video; + break; + default: + return -EINVAL; + } + + mnpcount = 0; + { + unsigned int mnp; + unsigned int xvco; + + for (mnp = g450_firstpll(minfo, pi, &xvco, fout); mnp != NO_MORE_MNP; mnp = g450_nextpll(minfo, pi, &xvco, mnp)) { + unsigned int idx; + unsigned int vco; + unsigned int delta; + + vco = g450_mnp2vco(minfo, mnp); + delta = pll_freq_delta(fout, g450_vco2f(mnp, vco)); + for (idx = mnpcount; idx > 0; idx--) { + /* == is important; due to nextpll algorithm we get + sorted equally good frequencies from lower VCO + frequency to higher - with <= lowest wins, while + with < highest one wins */ + if (delta <= deltaarray[idx-1]) { + /* all else being equal except VCO, + * choose VCO not near (within 1/16th or so) VCOmin + * (freqs near VCOmin aren't as stable) + */ + if (delta == deltaarray[idx-1] + && vco != g450_mnp2vco(minfo, mnparray[idx-1]) + && vco < (pi->vcomin * 17 / 16)) { + break; + } + mnparray[idx] = mnparray[idx-1]; + deltaarray[idx] = deltaarray[idx-1]; + } else { + break; + } + } + mnparray[idx] = mnp; + deltaarray[idx] = delta; + mnpcount++; + } + } + /* VideoPLL and PixelPLL matched: do nothing... In all other cases we should get at least one frequency */ + if (!mnpcount) { + return -EBUSY; + } + { + unsigned long flags; + unsigned int mnp; + + matroxfb_DAC_lock_irqsave(flags); + mnp = g450_checkcache(minfo, ci, mnparray[0]); + if (mnp != NO_MORE_MNP) { + matroxfb_g450_setpll_cond(minfo, mnp, pll); + } else { + mnp = g450_findworkingpll(minfo, pll, mnparray, mnpcount); + g450_addcache(ci, mnparray[0], mnp); + } + updatehwstate_clk(&minfo->hw, mnp, pll); + matroxfb_DAC_unlock_irqrestore(flags); + return mnp; + } +} + +/* It must be greater than number of possible PLL values. + * Currently there is 5(p) * 10(m) = 50 possible values. */ +#define MNP_TABLE_SIZE 64 + +int matroxfb_g450_setclk(struct matrox_fb_info *minfo, unsigned int fout, + unsigned int pll) +{ + unsigned int* arr; + + arr = kmalloc(sizeof(*arr) * MNP_TABLE_SIZE * 2, GFP_KERNEL); + if (arr) { + int r; + + r = __g450_setclk(minfo, fout, pll, arr, arr + MNP_TABLE_SIZE); + kfree(arr); + return r; + } + return -ENOMEM; +} + +EXPORT_SYMBOL(matroxfb_g450_setclk); +EXPORT_SYMBOL(g450_mnp2f); +EXPORT_SYMBOL(matroxfb_g450_setpll_cond); + +MODULE_AUTHOR("(c) 2001-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Matrox G450/G550 PLL driver"); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/g450_pll.h b/drivers/video/fbdev/matrox/g450_pll.h new file mode 100644 index 000000000..5303336c6 --- /dev/null +++ b/drivers/video/fbdev/matrox/g450_pll.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __G450_PLL_H__ +#define __G450_PLL_H__ + +#include "matroxfb_base.h" + +int matroxfb_g450_setclk(struct matrox_fb_info *minfo, unsigned int fout, + unsigned int pll); +unsigned int g450_mnp2f(const struct matrox_fb_info *minfo, unsigned int mnp); +void matroxfb_g450_setpll_cond(struct matrox_fb_info *minfo, unsigned int mnp, + unsigned int pll); + +#endif /* __G450_PLL_H__ */ diff --git a/drivers/video/fbdev/matrox/i2c-matroxfb.c b/drivers/video/fbdev/matrox/i2c-matroxfb.c new file mode 100644 index 000000000..e02dd11b0 --- /dev/null +++ b/drivers/video/fbdev/matrox/i2c-matroxfb.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Version: 1.64 2002/06/10 + * + * See matroxfb_base.c for contributors. + * + */ + +#include "matroxfb_base.h" +#include "matroxfb_maven.h" +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/i2c-algo-bit.h> + +/* MGA-TVO I2C for G200, G400 */ +#define MAT_CLK 0x20 +#define MAT_DATA 0x10 +/* primary head DDC for Mystique(?), G100, G200, G400 */ +#define DDC1_CLK 0x08 +#define DDC1_DATA 0x02 +/* primary head DDC for Millennium, Millennium II */ +#define DDC1B_CLK 0x10 +#define DDC1B_DATA 0x04 +/* secondary head DDC for G400 */ +#define DDC2_CLK 0x04 +#define DDC2_DATA 0x01 + +/******************************************************/ + +struct matroxfb_dh_maven_info { + struct i2c_bit_adapter maven; + struct i2c_bit_adapter ddc1; + struct i2c_bit_adapter ddc2; +}; + +static int matroxfb_read_gpio(struct matrox_fb_info* minfo) { + unsigned long flags; + int v; + + matroxfb_DAC_lock_irqsave(flags); + v = matroxfb_DAC_in(minfo, DAC_XGENIODATA); + matroxfb_DAC_unlock_irqrestore(flags); + return v; +} + +static void matroxfb_set_gpio(struct matrox_fb_info* minfo, int mask, int val) { + unsigned long flags; + int v; + + matroxfb_DAC_lock_irqsave(flags); + v = (matroxfb_DAC_in(minfo, DAC_XGENIOCTRL) & mask) | val; + matroxfb_DAC_out(minfo, DAC_XGENIOCTRL, v); + /* We must reset GENIODATA very often... XFree plays with this register */ + matroxfb_DAC_out(minfo, DAC_XGENIODATA, 0x00); + matroxfb_DAC_unlock_irqrestore(flags); +} + +/* software I2C functions */ +static inline void matroxfb_i2c_set(struct matrox_fb_info* minfo, int mask, int state) { + if (state) + state = 0; + else + state = mask; + matroxfb_set_gpio(minfo, ~mask, state); +} + +static void matroxfb_gpio_setsda(void* data, int state) { + struct i2c_bit_adapter* b = data; + matroxfb_i2c_set(b->minfo, b->mask.data, state); +} + +static void matroxfb_gpio_setscl(void* data, int state) { + struct i2c_bit_adapter* b = data; + matroxfb_i2c_set(b->minfo, b->mask.clock, state); +} + +static int matroxfb_gpio_getsda(void* data) { + struct i2c_bit_adapter* b = data; + return (matroxfb_read_gpio(b->minfo) & b->mask.data) ? 1 : 0; +} + +static int matroxfb_gpio_getscl(void* data) { + struct i2c_bit_adapter* b = data; + return (matroxfb_read_gpio(b->minfo) & b->mask.clock) ? 1 : 0; +} + +static const struct i2c_algo_bit_data matrox_i2c_algo_template = +{ + .setsda = matroxfb_gpio_setsda, + .setscl = matroxfb_gpio_setscl, + .getsda = matroxfb_gpio_getsda, + .getscl = matroxfb_gpio_getscl, + .udelay = 10, + .timeout = 100, +}; + +static int i2c_bus_reg(struct i2c_bit_adapter* b, struct matrox_fb_info* minfo, + unsigned int data, unsigned int clock, const char *name) +{ + int err; + + b->minfo = minfo; + b->mask.data = data; + b->mask.clock = clock; + b->adapter.owner = THIS_MODULE; + snprintf(b->adapter.name, sizeof(b->adapter.name), name, + minfo->fbcon.node); + i2c_set_adapdata(&b->adapter, b); + b->adapter.algo_data = &b->bac; + b->adapter.dev.parent = &minfo->pcidev->dev; + b->bac = matrox_i2c_algo_template; + b->bac.data = b; + err = i2c_bit_add_bus(&b->adapter); + b->initialized = !err; + return err; +} + +static void i2c_bit_bus_del(struct i2c_bit_adapter* b) { + if (b->initialized) { + i2c_del_adapter(&b->adapter); + b->initialized = 0; + } +} + +static inline void i2c_maven_done(struct matroxfb_dh_maven_info* minfo2) { + i2c_bit_bus_del(&minfo2->maven); +} + +static inline void i2c_ddc1_done(struct matroxfb_dh_maven_info* minfo2) { + i2c_bit_bus_del(&minfo2->ddc1); +} + +static inline void i2c_ddc2_done(struct matroxfb_dh_maven_info* minfo2) { + i2c_bit_bus_del(&minfo2->ddc2); +} + +static void* i2c_matroxfb_probe(struct matrox_fb_info* minfo) { + int err; + unsigned long flags; + struct matroxfb_dh_maven_info* m2info; + + m2info = kzalloc_obj(*m2info); + if (!m2info) + return NULL; + + matroxfb_DAC_lock_irqsave(flags); + matroxfb_DAC_out(minfo, DAC_XGENIODATA, 0xFF); + matroxfb_DAC_out(minfo, DAC_XGENIOCTRL, 0x00); + matroxfb_DAC_unlock_irqrestore(flags); + + switch (minfo->chip) { + case MGA_2064: + case MGA_2164: + err = i2c_bus_reg(&m2info->ddc1, minfo, + DDC1B_DATA, DDC1B_CLK, + "DDC:fb%u #0"); + break; + default: + err = i2c_bus_reg(&m2info->ddc1, minfo, + DDC1_DATA, DDC1_CLK, + "DDC:fb%u #0"); + break; + } + if (err) + goto fail_ddc1; + if (minfo->devflags.dualhead) { + err = i2c_bus_reg(&m2info->ddc2, minfo, DDC2_DATA, DDC2_CLK, "DDC:fb%u #1"); + if (err == -ENODEV) { + printk(KERN_INFO "i2c-matroxfb: VGA->TV plug detected, DDC unavailable.\n"); + } else if (err) + printk(KERN_INFO "i2c-matroxfb: Could not register secondary output i2c bus. Continuing anyway.\n"); + /* Register maven bus even on G450/G550 */ + err = i2c_bus_reg(&m2info->maven, minfo, MAT_DATA, MAT_CLK, "MAVEN:fb%u"); + if (err) + printk(KERN_INFO "i2c-matroxfb: Could not register Maven i2c bus. Continuing anyway.\n"); + else { + struct i2c_board_info maven_info = { + I2C_BOARD_INFO("maven", 0x1b), + }; + unsigned short const addr_list[2] = { + 0x1b, I2C_CLIENT_END + }; + + i2c_new_scanned_device(&m2info->maven.adapter, + &maven_info, addr_list, NULL); + } + } + return m2info; +fail_ddc1:; + kfree(m2info); + printk(KERN_ERR "i2c-matroxfb: Could not register primary adapter DDC bus.\n"); + return NULL; +} + +static void i2c_matroxfb_remove(struct matrox_fb_info* minfo, void* data) { + struct matroxfb_dh_maven_info* m2info = data; + + i2c_maven_done(m2info); + i2c_ddc2_done(m2info); + i2c_ddc1_done(m2info); + kfree(m2info); +} + +static struct matroxfb_driver i2c_matroxfb = { + .node = LIST_HEAD_INIT(i2c_matroxfb.node), + .name = "i2c-matroxfb", + .probe = i2c_matroxfb_probe, + .remove = i2c_matroxfb_remove, +}; + +static int __init i2c_matroxfb_init(void) { + if (matroxfb_register_driver(&i2c_matroxfb)) { + printk(KERN_ERR "i2c-matroxfb: failed to register driver\n"); + return -ENXIO; + } + return 0; +} + +static void __exit i2c_matroxfb_exit(void) { + matroxfb_unregister_driver(&i2c_matroxfb); +} + +MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Support module providing I2C buses present on Matrox videocards"); + +module_init(i2c_matroxfb_init); +module_exit(i2c_matroxfb_exit); +/* no __setup required */ +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_DAC1064.c b/drivers/video/fbdev/matrox/matroxfb_DAC1064.c new file mode 100644 index 000000000..9a893b70a --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_DAC1064.c @@ -0,0 +1,1116 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * See matroxfb_base.c for contributors. + * + */ + +#include <linux/export.h> + +#include "matroxfb_DAC1064.h" +#include "matroxfb_misc.h" +#include "matroxfb_accel.h" +#include "g450_pll.h" +#include <linux/matroxfb.h> + +#ifdef NEED_DAC1064 +#define outDAC1064 matroxfb_DAC_out +#define inDAC1064 matroxfb_DAC_in + +#define DAC1064_OPT_SCLK_PCI 0x00 +#define DAC1064_OPT_SCLK_PLL 0x01 +#define DAC1064_OPT_SCLK_EXT 0x02 +#define DAC1064_OPT_SCLK_MASK 0x03 +#define DAC1064_OPT_GDIV1 0x04 /* maybe it is GDIV2 on G100 ?! */ +#define DAC1064_OPT_GDIV3 0x00 +#define DAC1064_OPT_MDIV1 0x08 +#define DAC1064_OPT_MDIV2 0x00 +#define DAC1064_OPT_RESERVED 0x10 + +static void DAC1064_calcclock(const struct matrox_fb_info *minfo, + unsigned int freq, unsigned int fmax, + unsigned int *in, unsigned int *feed, + unsigned int *post) +{ + unsigned int fvco; + unsigned int p; + + DBG(__func__) + + /* only for devices older than G450 */ + + fvco = PLL_calcclock(minfo, freq, fmax, in, feed, &p); + + p = (1 << p) - 1; + if (fvco <= 100000) + ; + else if (fvco <= 140000) + p |= 0x08; + else if (fvco <= 180000) + p |= 0x10; + else + p |= 0x18; + *post = p; +} + +/* they must be in POS order */ +static const unsigned char MGA1064_DAC_regs[] = { + M1064_XCURADDL, M1064_XCURADDH, M1064_XCURCTRL, + M1064_XCURCOL0RED, M1064_XCURCOL0GREEN, M1064_XCURCOL0BLUE, + M1064_XCURCOL1RED, M1064_XCURCOL1GREEN, M1064_XCURCOL1BLUE, + M1064_XCURCOL2RED, M1064_XCURCOL2GREEN, M1064_XCURCOL2BLUE, + DAC1064_XVREFCTRL, M1064_XMULCTRL, M1064_XPIXCLKCTRL, M1064_XGENCTRL, + M1064_XMISCCTRL, + M1064_XGENIOCTRL, M1064_XGENIODATA, M1064_XZOOMCTRL, M1064_XSENSETEST, + M1064_XCRCBITSEL, + M1064_XCOLKEYMASKL, M1064_XCOLKEYMASKH, M1064_XCOLKEYL, M1064_XCOLKEYH }; + +static const unsigned char MGA1064_DAC[] = { + 0x00, 0x00, M1064_XCURCTRL_DIS, + 0x00, 0x00, 0x00, /* black */ + 0xFF, 0xFF, 0xFF, /* white */ + 0xFF, 0x00, 0x00, /* red */ + 0x00, 0, + M1064_XPIXCLKCTRL_PLL_UP | M1064_XPIXCLKCTRL_EN | M1064_XPIXCLKCTRL_SRC_PLL, + M1064_XGENCTRL_VS_0 | M1064_XGENCTRL_ALPHA_DIS | M1064_XGENCTRL_BLACK_0IRE | M1064_XGENCTRL_NO_SYNC_ON_GREEN, + M1064_XMISCCTRL_DAC_8BIT, + 0x00, 0x00, M1064_XZOOMCTRL_1, M1064_XSENSETEST_BCOMP | M1064_XSENSETEST_GCOMP | M1064_XSENSETEST_RCOMP | M1064_XSENSETEST_PDOWN, + 0x00, + 0x00, 0x00, 0xFF, 0xFF}; + +static void DAC1064_setpclk(struct matrox_fb_info *minfo, unsigned long fout) +{ + unsigned int m, n, p; + + DBG(__func__) + + DAC1064_calcclock(minfo, fout, minfo->max_pixel_clock, &m, &n, &p); + minfo->hw.DACclk[0] = m; + minfo->hw.DACclk[1] = n; + minfo->hw.DACclk[2] = p; +} + +static void DAC1064_setmclk(struct matrox_fb_info *minfo, int oscinfo, + unsigned long fmem) +{ + u_int32_t mx; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + if (minfo->devflags.noinit) { + /* read MCLK and give up... */ + hw->DACclk[3] = inDAC1064(minfo, DAC1064_XSYSPLLM); + hw->DACclk[4] = inDAC1064(minfo, DAC1064_XSYSPLLN); + hw->DACclk[5] = inDAC1064(minfo, DAC1064_XSYSPLLP); + return; + } + mx = hw->MXoptionReg | 0x00000004; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + mx &= ~0x000000BB; + if (oscinfo & DAC1064_OPT_GDIV1) + mx |= 0x00000008; + if (oscinfo & DAC1064_OPT_MDIV1) + mx |= 0x00000010; + if (oscinfo & DAC1064_OPT_RESERVED) + mx |= 0x00000080; + if ((oscinfo & DAC1064_OPT_SCLK_MASK) == DAC1064_OPT_SCLK_PLL) { + /* select PCI clock until we have setup oscilator... */ + int clk; + unsigned int m, n, p; + + /* powerup system PLL, select PCI clock */ + mx |= 0x00000020; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + mx &= ~0x00000004; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + + /* !!! you must not access device if MCLK is not running !!! + Doing so cause immediate PCI lockup :-( Maybe they should + generate ABORT or I/O (parity...) error and Linux should + recover from this... (kill driver/process). But world is not + perfect... */ + /* (bit 2 of PCI_OPTION_REG must be 0... and bits 0,1 must not + select PLL... because of PLL can be stopped at this time) */ + DAC1064_calcclock(minfo, fmem, minfo->max_pixel_clock, &m, &n, &p); + outDAC1064(minfo, DAC1064_XSYSPLLM, hw->DACclk[3] = m); + outDAC1064(minfo, DAC1064_XSYSPLLN, hw->DACclk[4] = n); + outDAC1064(minfo, DAC1064_XSYSPLLP, hw->DACclk[5] = p); + for (clk = 65536; clk; --clk) { + if (inDAC1064(minfo, DAC1064_XSYSPLLSTAT) & 0x40) + break; + } + if (!clk) + printk(KERN_ERR "matroxfb: aiee, SYSPLL not locked\n"); + /* select PLL */ + mx |= 0x00000005; + } else { + /* select specified system clock source */ + mx |= oscinfo & DAC1064_OPT_SCLK_MASK; + } + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + mx &= ~0x00000004; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mx); + hw->MXoptionReg = mx; +} + +#ifdef CONFIG_FB_MATROX_G +static void g450_set_plls(struct matrox_fb_info *minfo) +{ + u_int32_t c2_ctl; + unsigned int pxc; + struct matrox_hw_state *hw = &minfo->hw; + int pixelmnp; + int videomnp; + + c2_ctl = hw->crtc2.ctl & ~0x4007; /* Clear PLL + enable for CRTC2 */ + c2_ctl |= 0x0001; /* Enable CRTC2 */ + hw->DACreg[POS1064_XPWRCTRL] &= ~0x02; /* Stop VIDEO PLL */ + pixelmnp = minfo->crtc1.mnp; + videomnp = minfo->crtc2.mnp; + if (videomnp < 0) { + c2_ctl &= ~0x0001; /* Disable CRTC2 */ + hw->DACreg[POS1064_XPWRCTRL] &= ~0x10; /* Powerdown CRTC2 */ + } else if (minfo->crtc2.pixclock == minfo->features.pll.ref_freq) { + c2_ctl |= 0x4002; /* Use reference directly */ + } else if (videomnp == pixelmnp) { + c2_ctl |= 0x0004; /* Use pixel PLL */ + } else { + if (0 == ((videomnp ^ pixelmnp) & 0xFFFFFF00)) { + /* PIXEL and VIDEO PLL must not use same frequency. We modify N + of PIXEL PLL in such case because of VIDEO PLL may be source + of TVO clocks, and chroma subcarrier is derived from its + pixel clocks */ + pixelmnp += 0x000100; + } + c2_ctl |= 0x0006; /* Use video PLL */ + hw->DACreg[POS1064_XPWRCTRL] |= 0x02; + + outDAC1064(minfo, M1064_XPWRCTRL, hw->DACreg[POS1064_XPWRCTRL]); + matroxfb_g450_setpll_cond(minfo, videomnp, M_VIDEO_PLL); + } + + hw->DACreg[POS1064_XPIXCLKCTRL] &= ~M1064_XPIXCLKCTRL_PLL_UP; + if (pixelmnp >= 0) { + hw->DACreg[POS1064_XPIXCLKCTRL] |= M1064_XPIXCLKCTRL_PLL_UP; + + outDAC1064(minfo, M1064_XPIXCLKCTRL, hw->DACreg[POS1064_XPIXCLKCTRL]); + matroxfb_g450_setpll_cond(minfo, pixelmnp, M_PIXEL_PLL_C); + } + if (c2_ctl != hw->crtc2.ctl) { + hw->crtc2.ctl = c2_ctl; + mga_outl(0x3C10, c2_ctl); + } + + pxc = minfo->crtc1.pixclock; + if (pxc == 0 || minfo->outputs[2].src == MATROXFB_SRC_CRTC2) { + pxc = minfo->crtc2.pixclock; + } + if (minfo->chip == MGA_G550) { + if (pxc < 45000) { + hw->DACreg[POS1064_XPANMODE] = 0x00; /* 0-50 */ + } else if (pxc < 55000) { + hw->DACreg[POS1064_XPANMODE] = 0x08; /* 34-62 */ + } else if (pxc < 70000) { + hw->DACreg[POS1064_XPANMODE] = 0x10; /* 42-78 */ + } else if (pxc < 85000) { + hw->DACreg[POS1064_XPANMODE] = 0x18; /* 62-92 */ + } else if (pxc < 100000) { + hw->DACreg[POS1064_XPANMODE] = 0x20; /* 74-108 */ + } else if (pxc < 115000) { + hw->DACreg[POS1064_XPANMODE] = 0x28; /* 94-122 */ + } else if (pxc < 125000) { + hw->DACreg[POS1064_XPANMODE] = 0x30; /* 108-132 */ + } else { + hw->DACreg[POS1064_XPANMODE] = 0x38; /* 120-168 */ + } + } else { + /* G450 */ + if (pxc < 45000) { + hw->DACreg[POS1064_XPANMODE] = 0x00; /* 0-54 */ + } else if (pxc < 65000) { + hw->DACreg[POS1064_XPANMODE] = 0x08; /* 38-70 */ + } else if (pxc < 85000) { + hw->DACreg[POS1064_XPANMODE] = 0x10; /* 56-96 */ + } else if (pxc < 105000) { + hw->DACreg[POS1064_XPANMODE] = 0x18; /* 80-114 */ + } else if (pxc < 135000) { + hw->DACreg[POS1064_XPANMODE] = 0x20; /* 102-144 */ + } else if (pxc < 160000) { + hw->DACreg[POS1064_XPANMODE] = 0x28; /* 132-166 */ + } else if (pxc < 175000) { + hw->DACreg[POS1064_XPANMODE] = 0x30; /* 154-182 */ + } else { + hw->DACreg[POS1064_XPANMODE] = 0x38; /* 170-204 */ + } + } +} +#endif + +void DAC1064_global_init(struct matrox_fb_info *minfo) +{ + struct matrox_hw_state *hw = &minfo->hw; + + hw->DACreg[POS1064_XMISCCTRL] &= M1064_XMISCCTRL_DAC_WIDTHMASK; + hw->DACreg[POS1064_XMISCCTRL] |= M1064_XMISCCTRL_LUT_EN; + hw->DACreg[POS1064_XPIXCLKCTRL] = M1064_XPIXCLKCTRL_PLL_UP | M1064_XPIXCLKCTRL_EN | M1064_XPIXCLKCTRL_SRC_PLL; +#ifdef CONFIG_FB_MATROX_G + if (minfo->devflags.g450dac) { + hw->DACreg[POS1064_XPWRCTRL] = 0x1F; /* powerup everything */ + hw->DACreg[POS1064_XOUTPUTCONN] = 0x00; /* disable outputs */ + hw->DACreg[POS1064_XMISCCTRL] |= M1064_XMISCCTRL_DAC_EN; + switch (minfo->outputs[0].src) { + case MATROXFB_SRC_CRTC1: + case MATROXFB_SRC_CRTC2: + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x01; /* enable output; CRTC1/2 selection is in CRTC2 ctl */ + break; + case MATROXFB_SRC_NONE: + hw->DACreg[POS1064_XMISCCTRL] &= ~M1064_XMISCCTRL_DAC_EN; + break; + } + switch (minfo->outputs[1].src) { + case MATROXFB_SRC_CRTC1: + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x04; + break; + case MATROXFB_SRC_CRTC2: + if (minfo->outputs[1].mode == MATROXFB_OUTPUT_MODE_MONITOR) { + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x08; + } else { + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x0C; + } + break; + case MATROXFB_SRC_NONE: + hw->DACreg[POS1064_XPWRCTRL] &= ~0x01; /* Poweroff DAC2 */ + break; + } + switch (minfo->outputs[2].src) { + case MATROXFB_SRC_CRTC1: + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x20; + break; + case MATROXFB_SRC_CRTC2: + hw->DACreg[POS1064_XOUTPUTCONN] |= 0x40; + break; + case MATROXFB_SRC_NONE: +#if 0 + /* HELP! If we boot without DFP connected to DVI, we can + poweroff TMDS. But if we boot with DFP connected, + TMDS generated clocks are used instead of ALL pixclocks + available... If someone knows which register + handles it, please reveal this secret to me... */ + hw->DACreg[POS1064_XPWRCTRL] &= ~0x04; /* Poweroff TMDS */ +#endif + break; + } + /* Now set timming related variables... */ + g450_set_plls(minfo); + } else +#endif + { + if (minfo->outputs[1].src == MATROXFB_SRC_CRTC1) { + hw->DACreg[POS1064_XPIXCLKCTRL] = M1064_XPIXCLKCTRL_PLL_UP | M1064_XPIXCLKCTRL_EN | M1064_XPIXCLKCTRL_SRC_EXT; + hw->DACreg[POS1064_XMISCCTRL] |= GX00_XMISCCTRL_MFC_MAFC | G400_XMISCCTRL_VDO_MAFC12; + } else if (minfo->outputs[1].src == MATROXFB_SRC_CRTC2) { + hw->DACreg[POS1064_XMISCCTRL] |= GX00_XMISCCTRL_MFC_MAFC | G400_XMISCCTRL_VDO_C2_MAFC12; + } else if (minfo->outputs[2].src == MATROXFB_SRC_CRTC1) + hw->DACreg[POS1064_XMISCCTRL] |= GX00_XMISCCTRL_MFC_PANELLINK | G400_XMISCCTRL_VDO_MAFC12; + else + hw->DACreg[POS1064_XMISCCTRL] |= GX00_XMISCCTRL_MFC_DIS; + + if (minfo->outputs[0].src != MATROXFB_SRC_NONE) + hw->DACreg[POS1064_XMISCCTRL] |= M1064_XMISCCTRL_DAC_EN; + } +} + +void DAC1064_global_restore(struct matrox_fb_info *minfo) +{ + struct matrox_hw_state *hw = &minfo->hw; + + outDAC1064(minfo, M1064_XPIXCLKCTRL, hw->DACreg[POS1064_XPIXCLKCTRL]); + outDAC1064(minfo, M1064_XMISCCTRL, hw->DACreg[POS1064_XMISCCTRL]); + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) { + outDAC1064(minfo, 0x20, 0x04); + outDAC1064(minfo, 0x1F, minfo->devflags.dfp_type); + if (minfo->devflags.g450dac) { + outDAC1064(minfo, M1064_XSYNCCTRL, 0xCC); + outDAC1064(minfo, M1064_XPWRCTRL, hw->DACreg[POS1064_XPWRCTRL]); + outDAC1064(minfo, M1064_XPANMODE, hw->DACreg[POS1064_XPANMODE]); + outDAC1064(minfo, M1064_XOUTPUTCONN, hw->DACreg[POS1064_XOUTPUTCONN]); + } + } +} + +static int DAC1064_init_1(struct matrox_fb_info *minfo, struct my_timming *m) +{ + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + memcpy(hw->DACreg, MGA1064_DAC, sizeof(MGA1064_DAC_regs)); + switch (minfo->fbcon.var.bits_per_pixel) { + /* case 4: not supported by MGA1064 DAC */ + case 8: + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_8BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + break; + case 16: + if (minfo->fbcon.var.green.length == 5) + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_15BPP_1BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + else + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_16BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + break; + case 24: + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_24BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + break; + case 32: + hw->DACreg[POS1064_XMULCTRL] = M1064_XMULCTRL_DEPTH_32BPP | M1064_XMULCTRL_GRAPHICS_PALETIZED; + break; + default: + return 1; /* unsupported depth */ + } + hw->DACreg[POS1064_XVREFCTRL] = minfo->features.DAC1064.xvrefctrl; + hw->DACreg[POS1064_XGENCTRL] &= ~M1064_XGENCTRL_SYNC_ON_GREEN_MASK; + hw->DACreg[POS1064_XGENCTRL] |= (m->sync & FB_SYNC_ON_GREEN)?M1064_XGENCTRL_SYNC_ON_GREEN:M1064_XGENCTRL_NO_SYNC_ON_GREEN; + hw->DACreg[POS1064_XCURADDL] = 0; + hw->DACreg[POS1064_XCURADDH] = 0; + + DAC1064_global_init(minfo); + return 0; +} + +static int DAC1064_init_2(struct matrox_fb_info *minfo, struct my_timming *m) +{ + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + if (minfo->fbcon.var.bits_per_pixel > 16) { /* 256 entries */ + int i; + + for (i = 0; i < 256; i++) { + hw->DACpal[i * 3 + 0] = i; + hw->DACpal[i * 3 + 1] = i; + hw->DACpal[i * 3 + 2] = i; + } + } else if (minfo->fbcon.var.bits_per_pixel > 8) { + if (minfo->fbcon.var.green.length == 5) { /* 0..31, 128..159 */ + int i; + + for (i = 0; i < 32; i++) { + /* with p15 == 0 */ + hw->DACpal[i * 3 + 0] = i << 3; + hw->DACpal[i * 3 + 1] = i << 3; + hw->DACpal[i * 3 + 2] = i << 3; + /* with p15 == 1 */ + hw->DACpal[(i + 128) * 3 + 0] = i << 3; + hw->DACpal[(i + 128) * 3 + 1] = i << 3; + hw->DACpal[(i + 128) * 3 + 2] = i << 3; + } + } else { + int i; + + for (i = 0; i < 64; i++) { /* 0..63 */ + hw->DACpal[i * 3 + 0] = i << 3; + hw->DACpal[i * 3 + 1] = i << 2; + hw->DACpal[i * 3 + 2] = i << 3; + } + } + } else { + memset(hw->DACpal, 0, 768); + } + return 0; +} + +static void DAC1064_restore_1(struct matrox_fb_info *minfo) +{ + struct matrox_hw_state *hw = &minfo->hw; + + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + if ((inDAC1064(minfo, DAC1064_XSYSPLLM) != hw->DACclk[3]) || + (inDAC1064(minfo, DAC1064_XSYSPLLN) != hw->DACclk[4]) || + (inDAC1064(minfo, DAC1064_XSYSPLLP) != hw->DACclk[5])) { + outDAC1064(minfo, DAC1064_XSYSPLLM, hw->DACclk[3]); + outDAC1064(minfo, DAC1064_XSYSPLLN, hw->DACclk[4]); + outDAC1064(minfo, DAC1064_XSYSPLLP, hw->DACclk[5]); + } + { + unsigned int i; + + for (i = 0; i < sizeof(MGA1064_DAC_regs); i++) { + if ((i != POS1064_XPIXCLKCTRL) && (i != POS1064_XMISCCTRL)) + outDAC1064(minfo, MGA1064_DAC_regs[i], hw->DACreg[i]); + } + } + + DAC1064_global_restore(minfo); + + CRITEND +}; + +static void DAC1064_restore_2(struct matrox_fb_info *minfo) +{ +#ifdef DEBUG + unsigned int i; +#endif + + DBG(__func__) + +#ifdef DEBUG + dprintk(KERN_DEBUG "DAC1064regs "); + for (i = 0; i < sizeof(MGA1064_DAC_regs); i++) { + dprintk("R%02X=%02X ", MGA1064_DAC_regs[i], minfo->hw.DACreg[i]); + if ((i & 0x7) == 0x7) dprintk(KERN_DEBUG "continuing... "); + } + dprintk(KERN_DEBUG "DAC1064clk "); + for (i = 0; i < 6; i++) + dprintk("C%02X=%02X ", i, minfo->hw.DACclk[i]); + dprintk("\n"); +#endif +} + +static int m1064_compute(void* out, struct my_timming* m) { +#define minfo ((struct matrox_fb_info*)out) + { + int i; + int tmout; + CRITFLAGS + + DAC1064_setpclk(minfo, m->pixclock); + + CRITBEGIN + + for (i = 0; i < 3; i++) + outDAC1064(minfo, M1064_XPIXPLLCM + i, minfo->hw.DACclk[i]); + for (tmout = 500000; tmout; tmout--) { + if (inDAC1064(minfo, M1064_XPIXPLLSTAT) & 0x40) + break; + udelay(10); + } + + CRITEND + + if (!tmout) + printk(KERN_ERR "matroxfb: Pixel PLL not locked after 5 secs\n"); + } +#undef minfo + return 0; +} + +static struct matrox_altout m1064 = { + .name = "Primary output", + .compute = m1064_compute, +}; + +#ifdef CONFIG_FB_MATROX_G +static int g450_compute(void* out, struct my_timming* m) { +#define minfo ((struct matrox_fb_info*)out) + if (m->mnp < 0) { + m->mnp = matroxfb_g450_setclk(minfo, m->pixclock, (m->crtc == MATROXFB_SRC_CRTC1) ? M_PIXEL_PLL_C : M_VIDEO_PLL); + if (m->mnp >= 0) { + m->pixclock = g450_mnp2f(minfo, m->mnp); + } + } +#undef minfo + return 0; +} + +static struct matrox_altout g450out = { + .name = "Primary output", + .compute = g450_compute, +}; +#endif + +#endif /* NEED_DAC1064 */ + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static int MGA1064_init(struct matrox_fb_info *minfo, struct my_timming *m) +{ + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + if (DAC1064_init_1(minfo, m)) return 1; + if (matroxfb_vgaHWinit(minfo, m)) return 1; + + hw->MiscOutReg = 0xCB; + if (m->sync & FB_SYNC_HOR_HIGH_ACT) + hw->MiscOutReg &= ~0x40; + if (m->sync & FB_SYNC_VERT_HIGH_ACT) + hw->MiscOutReg &= ~0x80; + if (m->sync & FB_SYNC_COMP_HIGH_ACT) /* should be only FB_SYNC_COMP */ + hw->CRTCEXT[3] |= 0x40; + + if (DAC1064_init_2(minfo, m)) return 1; + return 0; +} +#endif + +#ifdef CONFIG_FB_MATROX_G +static int MGAG100_init(struct matrox_fb_info *minfo, struct my_timming *m) +{ + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + if (DAC1064_init_1(minfo, m)) return 1; + hw->MXoptionReg &= ~0x2000; + if (matroxfb_vgaHWinit(minfo, m)) return 1; + + hw->MiscOutReg = 0xEF; + if (m->sync & FB_SYNC_HOR_HIGH_ACT) + hw->MiscOutReg &= ~0x40; + if (m->sync & FB_SYNC_VERT_HIGH_ACT) + hw->MiscOutReg &= ~0x80; + if (m->sync & FB_SYNC_COMP_HIGH_ACT) /* should be only FB_SYNC_COMP */ + hw->CRTCEXT[3] |= 0x40; + + if (DAC1064_init_2(minfo, m)) return 1; + return 0; +} +#endif /* G */ + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static void MGA1064_ramdac_init(struct matrox_fb_info *minfo) +{ + + DBG(__func__) + + /* minfo->features.DAC1064.vco_freq_min = 120000; */ + minfo->features.pll.vco_freq_min = 62000; + minfo->features.pll.ref_freq = 14318; + minfo->features.pll.feed_div_min = 100; + minfo->features.pll.feed_div_max = 127; + minfo->features.pll.in_div_min = 1; + minfo->features.pll.in_div_max = 31; + minfo->features.pll.post_shift_max = 3; + minfo->features.DAC1064.xvrefctrl = DAC1064_XVREFCTRL_EXTERNAL; + /* maybe cmdline MCLK= ?, doc says gclk=44MHz, mclk=66MHz... it was 55/83 with old values */ + DAC1064_setmclk(minfo, DAC1064_OPT_MDIV2 | DAC1064_OPT_GDIV3 | DAC1064_OPT_SCLK_PLL, 133333); +} +#endif + +#ifdef CONFIG_FB_MATROX_G +/* BIOS environ */ +static int x7AF4 = 0x10; /* flags, maybe 0x10 = SDRAM, 0x00 = SGRAM??? */ + /* G100 wants 0x10, G200 SGRAM does not care... */ +#if 0 +static int def50 = 0; /* reg50, & 0x0F, & 0x3000 (only 0x0000, 0x1000, 0x2000 (0x3000 disallowed and treated as 0) */ +#endif + +static void MGAG100_progPixClock(const struct matrox_fb_info *minfo, int flags, + int m, int n, int p) +{ + int reg; + int selClk; + int clk; + + DBG(__func__) + + outDAC1064(minfo, M1064_XPIXCLKCTRL, inDAC1064(minfo, M1064_XPIXCLKCTRL) | M1064_XPIXCLKCTRL_DIS | + M1064_XPIXCLKCTRL_PLL_UP); + switch (flags & 3) { + case 0: reg = M1064_XPIXPLLAM; break; + case 1: reg = M1064_XPIXPLLBM; break; + default: reg = M1064_XPIXPLLCM; break; + } + outDAC1064(minfo, reg++, m); + outDAC1064(minfo, reg++, n); + outDAC1064(minfo, reg, p); + selClk = mga_inb(M_MISC_REG_READ) & ~0xC; + /* there should be flags & 0x03 & case 0/1/else */ + /* and we should first select source and after that we should wait for PLL */ + /* and we are waiting for PLL with oscilator disabled... Is it right? */ + switch (flags & 0x03) { + case 0x00: break; + case 0x01: selClk |= 4; break; + default: selClk |= 0x0C; break; + } + mga_outb(M_MISC_REG, selClk); + for (clk = 500000; clk; clk--) { + if (inDAC1064(minfo, M1064_XPIXPLLSTAT) & 0x40) + break; + udelay(10); + } + if (!clk) + printk(KERN_ERR "matroxfb: Pixel PLL%c not locked after usual time\n", (reg-M1064_XPIXPLLAM-2)/4 + 'A'); + selClk = inDAC1064(minfo, M1064_XPIXCLKCTRL) & ~M1064_XPIXCLKCTRL_SRC_MASK; + switch (flags & 0x0C) { + case 0x00: selClk |= M1064_XPIXCLKCTRL_SRC_PCI; break; + case 0x04: selClk |= M1064_XPIXCLKCTRL_SRC_PLL; break; + default: selClk |= M1064_XPIXCLKCTRL_SRC_EXT; break; + } + outDAC1064(minfo, M1064_XPIXCLKCTRL, selClk); + outDAC1064(minfo, M1064_XPIXCLKCTRL, inDAC1064(minfo, M1064_XPIXCLKCTRL) & ~M1064_XPIXCLKCTRL_DIS); +} + +static void MGAG100_setPixClock(const struct matrox_fb_info *minfo, int flags, + int freq) +{ + unsigned int m, n, p; + + DBG(__func__) + + DAC1064_calcclock(minfo, freq, minfo->max_pixel_clock, &m, &n, &p); + MGAG100_progPixClock(minfo, flags, m, n, p); +} +#endif + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static int MGA1064_preinit(struct matrox_fb_info *minfo) +{ + static const int vxres_mystique[] = { 512, 640, 768, 800, 832, 960, + 1024, 1152, 1280, 1600, 1664, 1920, + 2048, 0}; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + /* minfo->capable.cfb4 = 0; ... preinitialized by 0 */ + minfo->capable.text = 1; + minfo->capable.vxres = vxres_mystique; + + minfo->outputs[0].output = &m1064; + minfo->outputs[0].src = minfo->outputs[0].default_src; + minfo->outputs[0].data = minfo; + minfo->outputs[0].mode = MATROXFB_OUTPUT_MODE_MONITOR; + + if (minfo->devflags.noinit) + return 0; /* do not modify settings */ + hw->MXoptionReg &= 0xC0000100; + hw->MXoptionReg |= 0x00094E20; + if (minfo->devflags.novga) + hw->MXoptionReg &= ~0x00000100; + if (minfo->devflags.nobios) + hw->MXoptionReg &= ~0x40000000; + if (minfo->devflags.nopciretry) + hw->MXoptionReg |= 0x20000000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + mga_setr(M_SEQ_INDEX, 0x01, 0x20); + mga_outl(M_CTLWTST, 0x00000000); + udelay(200); + mga_outl(M_MACCESS, 0x00008000); + udelay(100); + mga_outl(M_MACCESS, 0x0000C000); + return 0; +} + +static void MGA1064_reset(struct matrox_fb_info *minfo) +{ + + DBG(__func__); + + MGA1064_ramdac_init(minfo); +} +#endif + +#ifdef CONFIG_FB_MATROX_G +static void g450_mclk_init(struct matrox_fb_info *minfo) +{ + /* switch all clocks to PCI source */ + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg | 4); + pci_write_config_dword(minfo->pcidev, PCI_OPTION3_REG, minfo->values.reg.opt3 & ~0x00300C03); + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + if (((minfo->values.reg.opt3 & 0x000003) == 0x000003) || + ((minfo->values.reg.opt3 & 0x000C00) == 0x000C00) || + ((minfo->values.reg.opt3 & 0x300000) == 0x300000)) { + matroxfb_g450_setclk(minfo, minfo->values.pll.video, M_VIDEO_PLL); + } else { + unsigned long flags; + unsigned int pwr; + + matroxfb_DAC_lock_irqsave(flags); + pwr = inDAC1064(minfo, M1064_XPWRCTRL) & ~0x02; + outDAC1064(minfo, M1064_XPWRCTRL, pwr); + matroxfb_DAC_unlock_irqrestore(flags); + } + matroxfb_g450_setclk(minfo, minfo->values.pll.system, M_SYSTEM_PLL); + + /* switch clocks to their real PLL source(s) */ + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg | 4); + pci_write_config_dword(minfo->pcidev, PCI_OPTION3_REG, minfo->values.reg.opt3); + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + +} + +static void g450_memory_init(struct matrox_fb_info *minfo) +{ + /* disable memory refresh */ + minfo->hw.MXoptionReg &= ~0x001F8000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + /* set memory interface parameters */ + minfo->hw.MXoptionReg &= ~0x00207E00; + minfo->hw.MXoptionReg |= 0x00207E00 & minfo->values.reg.opt; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, minfo->values.reg.opt2); + + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst); + + /* first set up memory interface with disabled memory interface clocks */ + pci_write_config_dword(minfo->pcidev, PCI_MEMMISC_REG, minfo->values.reg.memmisc & ~0x80000000U); + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk); + mga_outl(M_MACCESS, minfo->values.reg.maccess); + /* start memory clocks */ + pci_write_config_dword(minfo->pcidev, PCI_MEMMISC_REG, minfo->values.reg.memmisc | 0x80000000U); + + udelay(200); + + if (minfo->values.memory.ddr && (!minfo->values.memory.emrswen || !minfo->values.memory.dll)) { + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk & ~0x1000); + } + mga_outl(M_MACCESS, minfo->values.reg.maccess | 0x8000); + + udelay(200); + + minfo->hw.MXoptionReg |= 0x001F8000 & minfo->values.reg.opt; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + /* value is written to memory chips only if old != new */ + mga_outl(M_PLNWT, 0); + mga_outl(M_PLNWT, ~0); + + if (minfo->values.reg.mctlwtst != minfo->values.reg.mctlwtst_core) { + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst_core); + } + +} + +static void g450_preinit(struct matrox_fb_info *minfo) +{ + u_int32_t c2ctl; + u_int8_t curctl; + u_int8_t c1ctl; + + /* minfo->hw.MXoptionReg = minfo->values.reg.opt; */ + minfo->hw.MXoptionReg &= 0xC0000100; + minfo->hw.MXoptionReg |= 0x00000020; + if (minfo->devflags.novga) + minfo->hw.MXoptionReg &= ~0x00000100; + if (minfo->devflags.nobios) + minfo->hw.MXoptionReg &= ~0x40000000; + if (minfo->devflags.nopciretry) + minfo->hw.MXoptionReg |= 0x20000000; + minfo->hw.MXoptionReg |= minfo->values.reg.opt & 0x03400040; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + /* Init system clocks */ + + /* stop crtc2 */ + c2ctl = mga_inl(M_C2CTL); + mga_outl(M_C2CTL, c2ctl & ~1); + /* stop cursor */ + curctl = inDAC1064(minfo, M1064_XCURCTRL); + outDAC1064(minfo, M1064_XCURCTRL, 0); + /* stop crtc1 */ + c1ctl = mga_readr(M_SEQ_INDEX, 1); + mga_setr(M_SEQ_INDEX, 1, c1ctl | 0x20); + + g450_mclk_init(minfo); + g450_memory_init(minfo); + + /* set legacy VGA clock sources for DOSEmu or VMware... */ + matroxfb_g450_setclk(minfo, 25175, M_PIXEL_PLL_A); + matroxfb_g450_setclk(minfo, 28322, M_PIXEL_PLL_B); + + /* restore crtc1 */ + mga_setr(M_SEQ_INDEX, 1, c1ctl); + + /* restore cursor */ + outDAC1064(minfo, M1064_XCURCTRL, curctl); + + /* restore crtc2 */ + mga_outl(M_C2CTL, c2ctl); + + return; +} + +static int MGAG100_preinit(struct matrox_fb_info *minfo) +{ + static const int vxres_g100[] = { 512, 640, 768, 800, 832, 960, + 1024, 1152, 1280, 1600, 1664, 1920, + 2048, 0}; + struct matrox_hw_state *hw = &minfo->hw; + + u_int32_t reg50; +#if 0 + u_int32_t q; +#endif + + DBG(__func__) + + /* there are some instabilities if in_div > 19 && vco < 61000 */ + if (minfo->devflags.g450dac) { + minfo->features.pll.vco_freq_min = 130000; /* my sample: >118 */ + } else { + minfo->features.pll.vco_freq_min = 62000; + } + if (!minfo->features.pll.ref_freq) { + minfo->features.pll.ref_freq = 27000; + } + minfo->features.pll.feed_div_min = 7; + minfo->features.pll.feed_div_max = 127; + minfo->features.pll.in_div_min = 1; + minfo->features.pll.in_div_max = 31; + minfo->features.pll.post_shift_max = 3; + minfo->features.DAC1064.xvrefctrl = DAC1064_XVREFCTRL_G100_DEFAULT; + /* minfo->capable.cfb4 = 0; ... preinitialized by 0 */ + minfo->capable.text = 1; + minfo->capable.vxres = vxres_g100; + minfo->capable.plnwt = minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG100 + ? minfo->devflags.sgram : 1; + + if (minfo->devflags.g450dac) { + minfo->outputs[0].output = &g450out; + } else { + minfo->outputs[0].output = &m1064; + } + minfo->outputs[0].src = minfo->outputs[0].default_src; + minfo->outputs[0].data = minfo; + minfo->outputs[0].mode = MATROXFB_OUTPUT_MODE_MONITOR; + + if (minfo->devflags.g450dac) { + /* we must do this always, BIOS does not do it for us + and accelerator dies without it */ + mga_outl(0x1C0C, 0); + } + if (minfo->devflags.noinit) + return 0; + if (minfo->devflags.g450dac) { + g450_preinit(minfo); + return 0; + } + hw->MXoptionReg &= 0xC0000100; + hw->MXoptionReg |= 0x00000020; + if (minfo->devflags.novga) + hw->MXoptionReg &= ~0x00000100; + if (minfo->devflags.nobios) + hw->MXoptionReg &= ~0x40000000; + if (minfo->devflags.nopciretry) + hw->MXoptionReg |= 0x20000000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + DAC1064_setmclk(minfo, DAC1064_OPT_MDIV2 | DAC1064_OPT_GDIV3 | DAC1064_OPT_SCLK_PCI, 133333); + + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG100) { + pci_read_config_dword(minfo->pcidev, PCI_OPTION2_REG, ®50); + reg50 &= ~0x3000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, reg50); + + hw->MXoptionReg |= 0x1080; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst); + udelay(100); + mga_outb(0x1C05, 0x00); + mga_outb(0x1C05, 0x80); + udelay(100); + mga_outb(0x1C05, 0x40); + mga_outb(0x1C05, 0xC0); + udelay(100); + reg50 &= ~0xFF; + reg50 |= 0x07; + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, reg50); + /* it should help with G100 */ + mga_outb(M_GRAPHICS_INDEX, 6); + mga_outb(M_GRAPHICS_DATA, (mga_inb(M_GRAPHICS_DATA) & 3) | 4); + mga_setr(M_EXTVGA_INDEX, 0x03, 0x81); + mga_setr(M_EXTVGA_INDEX, 0x04, 0x00); + mga_writeb(minfo->video.vbase, 0x0000, 0xAA); + mga_writeb(minfo->video.vbase, 0x0800, 0x55); + mga_writeb(minfo->video.vbase, 0x4000, 0x55); +#if 0 + if (mga_readb(minfo->video.vbase, 0x0000) != 0xAA) { + hw->MXoptionReg &= ~0x1000; + } +#endif + hw->MXoptionReg |= 0x00078020; + } else if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG200) { + pci_read_config_dword(minfo->pcidev, PCI_OPTION2_REG, ®50); + reg50 &= ~0x3000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, reg50); + + if (minfo->devflags.memtype == -1) + hw->MXoptionReg |= minfo->values.reg.opt & 0x1C00; + else + hw->MXoptionReg |= (minfo->devflags.memtype & 7) << 10; + if (minfo->devflags.sgram) + hw->MXoptionReg |= 0x4000; + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst); + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk); + udelay(200); + mga_outl(M_MACCESS, 0x00000000); + mga_outl(M_MACCESS, 0x00008000); + udelay(100); + mga_outw(M_MEMRDBK, minfo->values.reg.memrdbk); + hw->MXoptionReg |= 0x00078020; + } else { + pci_read_config_dword(minfo->pcidev, PCI_OPTION2_REG, ®50); + reg50 &= ~0x00000100; + reg50 |= 0x00000000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION2_REG, reg50); + + if (minfo->devflags.memtype == -1) + hw->MXoptionReg |= minfo->values.reg.opt & 0x1C00; + else + hw->MXoptionReg |= (minfo->devflags.memtype & 7) << 10; + if (minfo->devflags.sgram) + hw->MXoptionReg |= 0x4000; + mga_outl(M_CTLWTST, minfo->values.reg.mctlwtst); + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk); + udelay(200); + mga_outl(M_MACCESS, 0x00000000); + mga_outl(M_MACCESS, 0x00008000); + udelay(100); + mga_outl(M_MEMRDBK, minfo->values.reg.memrdbk); + hw->MXoptionReg |= 0x00040020; + } + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + return 0; +} + +static void MGAG100_reset(struct matrox_fb_info *minfo) +{ + u_int8_t b; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + { +#ifdef G100_BROKEN_IBM_82351 + u_int32_t d; + + find 1014/22 (IBM/82351); /* if found and bridging Matrox, do some strange stuff */ + pci_read_config_byte(ibm, PCI_SECONDARY_BUS, &b); + if (b == minfo->pcidev->bus->number) { + pci_write_config_byte(ibm, PCI_COMMAND+1, 0); /* disable back-to-back & SERR */ + pci_write_config_byte(ibm, 0x41, 0xF4); /* ??? */ + pci_write_config_byte(ibm, PCI_IO_BASE, 0xF0); /* ??? */ + pci_write_config_byte(ibm, PCI_IO_LIMIT, 0x00); /* ??? */ + } +#endif + if (!minfo->devflags.noinit) { + if (x7AF4 & 8) { + hw->MXoptionReg |= 0x40; /* FIXME... */ + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + } + mga_setr(M_EXTVGA_INDEX, 0x06, 0x00); + } + } + if (minfo->devflags.g450dac) { + /* either leave MCLK as is... or they were set in preinit */ + hw->DACclk[3] = inDAC1064(minfo, DAC1064_XSYSPLLM); + hw->DACclk[4] = inDAC1064(minfo, DAC1064_XSYSPLLN); + hw->DACclk[5] = inDAC1064(minfo, DAC1064_XSYSPLLP); + } else { + DAC1064_setmclk(minfo, DAC1064_OPT_RESERVED | DAC1064_OPT_MDIV2 | DAC1064_OPT_GDIV1 | DAC1064_OPT_SCLK_PLL, 133333); + } + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) { + if (minfo->devflags.dfp_type == -1) { + minfo->devflags.dfp_type = inDAC1064(minfo, 0x1F); + } + } + if (minfo->devflags.noinit) + return; + if (minfo->devflags.g450dac) { + } else { + MGAG100_setPixClock(minfo, 4, 25175); + MGAG100_setPixClock(minfo, 5, 28322); + if (x7AF4 & 0x10) { + b = inDAC1064(minfo, M1064_XGENIODATA) & ~1; + outDAC1064(minfo, M1064_XGENIODATA, b); + b = inDAC1064(minfo, M1064_XGENIOCTRL) | 1; + outDAC1064(minfo, M1064_XGENIOCTRL, b); + } + } +} +#endif + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static void MGA1064_restore(struct matrox_fb_info *minfo) +{ + int i; + struct matrox_hw_state *hw = &minfo->hw; + + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + mga_outb(M_IEN, 0x00); + mga_outb(M_CACHEFLUSH, 0x00); + + CRITEND + + DAC1064_restore_1(minfo); + matroxfb_vgaHWrestore(minfo); + minfo->crtc1.panpos = -1; + for (i = 0; i < 6; i++) + mga_setr(M_EXTVGA_INDEX, i, hw->CRTCEXT[i]); + DAC1064_restore_2(minfo); +} +#endif + +#ifdef CONFIG_FB_MATROX_G +static void MGAG100_restore(struct matrox_fb_info *minfo) +{ + int i; + struct matrox_hw_state *hw = &minfo->hw; + + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + CRITEND + + DAC1064_restore_1(minfo); + matroxfb_vgaHWrestore(minfo); + if (minfo->devflags.support32MB) + mga_setr(M_EXTVGA_INDEX, 8, hw->CRTCEXT[8]); + minfo->crtc1.panpos = -1; + for (i = 0; i < 6; i++) + mga_setr(M_EXTVGA_INDEX, i, hw->CRTCEXT[i]); + DAC1064_restore_2(minfo); +} +#endif + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +struct matrox_switch matrox_mystique = { + .preinit = MGA1064_preinit, + .reset = MGA1064_reset, + .init = MGA1064_init, + .restore = MGA1064_restore, +}; +EXPORT_SYMBOL(matrox_mystique); +#endif + +#ifdef CONFIG_FB_MATROX_G +struct matrox_switch matrox_G100 = { + .preinit = MGAG100_preinit, + .reset = MGAG100_reset, + .init = MGAG100_init, + .restore = MGAG100_restore, +}; +EXPORT_SYMBOL(matrox_G100); +#endif + +#ifdef NEED_DAC1064 +EXPORT_SYMBOL(DAC1064_global_init); +EXPORT_SYMBOL(DAC1064_global_restore); +#endif +MODULE_DESCRIPTION("Matrox Mystique/G100 output driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_DAC1064.h b/drivers/video/fbdev/matrox/matroxfb_DAC1064.h new file mode 100644 index 000000000..3b2a6fd35 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_DAC1064.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MATROXFB_DAC1064_H__ +#define __MATROXFB_DAC1064_H__ + + +#include "matroxfb_base.h" + +#ifdef CONFIG_FB_MATROX_MYSTIQUE +extern struct matrox_switch matrox_mystique; +#endif +#ifdef CONFIG_FB_MATROX_G +extern struct matrox_switch matrox_G100; +#endif +#ifdef NEED_DAC1064 +void DAC1064_global_init(struct matrox_fb_info *minfo); +void DAC1064_global_restore(struct matrox_fb_info *minfo); +#endif + +#define M1064_INDEX 0x00 +#define M1064_PALWRADD 0x00 +#define M1064_PALDATA 0x01 +#define M1064_PIXRDMSK 0x02 +#define M1064_PALRDADD 0x03 +#define M1064_X_DATAREG 0x0A +#define M1064_CURPOSXL 0x0C /* can be accessed as DWORD */ +#define M1064_CURPOSXH 0x0D +#define M1064_CURPOSYL 0x0E +#define M1064_CURPOSYH 0x0F + +#define M1064_XCURADDL 0x04 +#define M1064_XCURADDH 0x05 +#define M1064_XCURCTRL 0x06 +#define M1064_XCURCTRL_DIS 0x00 /* transparent, transparent, transparent, transparent */ +#define M1064_XCURCTRL_3COLOR 0x01 /* transparent, 0, 1, 2 */ +#define M1064_XCURCTRL_XGA 0x02 /* 0, 1, transparent, complement */ +#define M1064_XCURCTRL_XWIN 0x03 /* transparent, transparent, 0, 1 */ + /* drive DVI by standard(0)/DVI(1) PLL */ + /* if set(1), C?DVICLKEN and C?DVICLKSEL must be set(1) */ +#define M1064_XDVICLKCTRL_DVIDATAPATHSEL 0x01 + /* drive CRTC1 by standard(0)/DVI(1) PLL */ +#define M1064_XDVICLKCTRL_C1DVICLKSEL 0x02 + /* drive CRTC2 by standard(0)/DVI(1) PLL */ +#define M1064_XDVICLKCTRL_C2DVICLKSEL 0x04 + /* pixel clock allowed to(0)/blocked from(1) driving CRTC1 */ +#define M1064_XDVICLKCTRL_C1DVICLKEN 0x08 + /* DVI PLL loop filter bandwidth selection bits */ +#define M1064_XDVICLKCTRL_DVILOOPCTL 0x30 + /* CRTC2 pixel clock allowed to(0)/blocked from(1) driving CRTC2 */ +#define M1064_XDVICLKCTRL_C2DVICLKEN 0x40 + /* P1PLL loop filter bandwidth selection */ +#define M1064_XDVICLKCTRL_P1LOOPBWDTCTL 0x80 +#define M1064_XCURCOL0RED 0x08 +#define M1064_XCURCOL0GREEN 0x09 +#define M1064_XCURCOL0BLUE 0x0A +#define M1064_XCURCOL1RED 0x0C +#define M1064_XCURCOL1GREEN 0x0D +#define M1064_XCURCOL1BLUE 0x0E +#define M1064_XDVICLKCTRL 0x0F +#define M1064_XCURCOL2RED 0x10 +#define M1064_XCURCOL2GREEN 0x11 +#define M1064_XCURCOL2BLUE 0x12 +#define DAC1064_XVREFCTRL 0x18 +#define DAC1064_XVREFCTRL_INTERNAL 0x3F +#define DAC1064_XVREFCTRL_EXTERNAL 0x00 +#define DAC1064_XVREFCTRL_G100_DEFAULT 0x03 +#define M1064_XMULCTRL 0x19 +#define M1064_XMULCTRL_DEPTH_8BPP 0x00 /* 8 bpp paletized */ +#define M1064_XMULCTRL_DEPTH_15BPP_1BPP 0x01 /* 15 bpp paletized + 1 bpp overlay */ +#define M1064_XMULCTRL_DEPTH_16BPP 0x02 /* 16 bpp paletized */ +#define M1064_XMULCTRL_DEPTH_24BPP 0x03 /* 24 bpp paletized */ +#define M1064_XMULCTRL_DEPTH_24BPP_8BPP 0x04 /* 24 bpp direct + 8 bpp overlay paletized */ +#define M1064_XMULCTRL_2G8V16 0x05 /* 15 bpp video direct, half xres, 8bpp paletized */ +#define M1064_XMULCTRL_G16V16 0x06 /* 15 bpp video, 15bpp graphics, one of them paletized */ +#define M1064_XMULCTRL_DEPTH_32BPP 0x07 /* 24 bpp paletized + 8 bpp unused */ +#define M1064_XMULCTRL_GRAPHICS_PALETIZED 0x00 +#define M1064_XMULCTRL_VIDEO_PALETIZED 0x08 +#define M1064_XPIXCLKCTRL 0x1A +#define M1064_XPIXCLKCTRL_SRC_PCI 0x00 +#define M1064_XPIXCLKCTRL_SRC_PLL 0x01 +#define M1064_XPIXCLKCTRL_SRC_EXT 0x02 +#define M1064_XPIXCLKCTRL_SRC_SYS 0x03 /* G200/G400 */ +#define M1064_XPIXCLKCTRL_SRC_PLL2 0x03 /* G450 */ +#define M1064_XPIXCLKCTRL_SRC_MASK 0x03 +#define M1064_XPIXCLKCTRL_EN 0x00 +#define M1064_XPIXCLKCTRL_DIS 0x04 +#define M1064_XPIXCLKCTRL_PLL_DOWN 0x00 +#define M1064_XPIXCLKCTRL_PLL_UP 0x08 +#define M1064_XGENCTRL 0x1D +#define M1064_XGENCTRL_VS_0 0x00 +#define M1064_XGENCTRL_VS_1 0x01 +#define M1064_XGENCTRL_ALPHA_DIS 0x00 +#define M1064_XGENCTRL_ALPHA_EN 0x02 +#define M1064_XGENCTRL_BLACK_0IRE 0x00 +#define M1064_XGENCTRL_BLACK_75IRE 0x10 +#define M1064_XGENCTRL_SYNC_ON_GREEN 0x00 +#define M1064_XGENCTRL_NO_SYNC_ON_GREEN 0x20 +#define M1064_XGENCTRL_SYNC_ON_GREEN_MASK 0x20 +#define M1064_XMISCCTRL 0x1E +#define M1064_XMISCCTRL_DAC_DIS 0x00 +#define M1064_XMISCCTRL_DAC_EN 0x01 +#define M1064_XMISCCTRL_MFC_VGA 0x00 +#define M1064_XMISCCTRL_MFC_MAFC 0x02 +#define M1064_XMISCCTRL_MFC_DIS 0x06 +#define GX00_XMISCCTRL_MFC_MAFC 0x02 +#define GX00_XMISCCTRL_MFC_PANELLINK 0x04 +#define GX00_XMISCCTRL_MFC_DIS 0x06 +#define GX00_XMISCCTRL_MFC_MASK 0x06 +#define M1064_XMISCCTRL_DAC_6BIT 0x00 +#define M1064_XMISCCTRL_DAC_8BIT 0x08 +#define M1064_XMISCCTRL_DAC_WIDTHMASK 0x08 +#define M1064_XMISCCTRL_LUT_DIS 0x00 +#define M1064_XMISCCTRL_LUT_EN 0x10 +#define G400_XMISCCTRL_VDO_MAFC12 0x00 +#define G400_XMISCCTRL_VDO_BYPASS656 0x40 +#define G400_XMISCCTRL_VDO_C2_MAFC12 0x80 +#define G400_XMISCCTRL_VDO_C2_BYPASS656 0xC0 +#define G400_XMISCCTRL_VDO_MASK 0xE0 +#define M1064_XGENIOCTRL 0x2A +#define M1064_XGENIODATA 0x2B +#define DAC1064_XSYSPLLM 0x2C +#define DAC1064_XSYSPLLN 0x2D +#define DAC1064_XSYSPLLP 0x2E +#define DAC1064_XSYSPLLSTAT 0x2F +#define M1064_XZOOMCTRL 0x38 +#define M1064_XZOOMCTRL_1 0x00 +#define M1064_XZOOMCTRL_2 0x01 +#define M1064_XZOOMCTRL_4 0x03 +#define M1064_XSENSETEST 0x3A +#define M1064_XSENSETEST_BCOMP 0x01 +#define M1064_XSENSETEST_GCOMP 0x02 +#define M1064_XSENSETEST_RCOMP 0x04 +#define M1064_XSENSETEST_PDOWN 0x00 +#define M1064_XSENSETEST_PUP 0x80 +#define M1064_XCRCREML 0x3C +#define M1064_XCRCREMH 0x3D +#define M1064_XCRCBITSEL 0x3E +#define M1064_XCOLKEYMASKL 0x40 +#define M1064_XCOLKEYMASKH 0x41 +#define M1064_XCOLKEYL 0x42 +#define M1064_XCOLKEYH 0x43 +#define M1064_XPIXPLLAM 0x44 +#define M1064_XPIXPLLAN 0x45 +#define M1064_XPIXPLLAP 0x46 +#define M1064_XPIXPLLBM 0x48 +#define M1064_XPIXPLLBN 0x49 +#define M1064_XPIXPLLBP 0x4A +#define M1064_XPIXPLLCM 0x4C +#define M1064_XPIXPLLCN 0x4D +#define M1064_XPIXPLLCP 0x4E +#define M1064_XPIXPLLSTAT 0x4F + +#define M1064_XTVO_IDX 0x87 +#define M1064_XTVO_DATA 0x88 + +#define M1064_XOUTPUTCONN 0x8A +#define M1064_XSYNCCTRL 0x8B +#define M1064_XVIDPLLSTAT 0x8C +#define M1064_XVIDPLLP 0x8D +#define M1064_XVIDPLLM 0x8E +#define M1064_XVIDPLLN 0x8F + +#define M1064_XPWRCTRL 0xA0 +#define M1064_XPWRCTRL_PANELPDN 0x04 + +#define M1064_XPANMODE 0xA2 + +enum POS1064 { + POS1064_XCURADDL=0, POS1064_XCURADDH, POS1064_XCURCTRL, + POS1064_XCURCOL0RED, POS1064_XCURCOL0GREEN, POS1064_XCURCOL0BLUE, + POS1064_XCURCOL1RED, POS1064_XCURCOL1GREEN, POS1064_XCURCOL1BLUE, + POS1064_XCURCOL2RED, POS1064_XCURCOL2GREEN, POS1064_XCURCOL2BLUE, + POS1064_XVREFCTRL, POS1064_XMULCTRL, POS1064_XPIXCLKCTRL, POS1064_XGENCTRL, + POS1064_XMISCCTRL, + POS1064_XGENIOCTRL, POS1064_XGENIODATA, POS1064_XZOOMCTRL, POS1064_XSENSETEST, + POS1064_XCRCBITSEL, + POS1064_XCOLKEYMASKL, POS1064_XCOLKEYMASKH, POS1064_XCOLKEYL, POS1064_XCOLKEYH, + POS1064_XOUTPUTCONN, POS1064_XPANMODE, POS1064_XPWRCTRL }; + + +#endif /* __MATROXFB_DAC1064_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_Ti3026.c b/drivers/video/fbdev/matrox/matroxfb_Ti3026.c new file mode 100644 index 000000000..4eb636cd1 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_Ti3026.c @@ -0,0 +1,751 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200 and G400 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * MTRR stuff: 1998 Tom Rini <trini@kernel.crashing.org> + * + * Contributors: "menion?" <menion@mindless.com> + * Betatesting, fixes, ideas + * + * "Kurt Garloff" <garloff@suse.de> + * Betatesting, fixes, ideas, videomodes, videomodes timmings + * + * "Tom Rini" <trini@kernel.crashing.org> + * MTRR stuff, PPC cleanups, betatesting, fixes, ideas + * + * "Bibek Sahu" <scorpio@dodds.net> + * Access device through readb|w|l and write b|w|l + * Extensive debugging stuff + * + * "Daniel Haun" <haund@usa.net> + * Testing, hardware cursor fixes + * + * "Scott Wood" <sawst46+@pitt.edu> + * Fixes + * + * "Gerd Knorr" <kraxel@goldbach.isdn.cs.tu-berlin.de> + * Betatesting + * + * "Kelly French" <targon@hazmat.com> + * "Fernando Herrera" <fherrera@eurielec.etsit.upm.es> + * Betatesting, bug reporting + * + * "Pablo Bianucci" <pbian@pccp.com.ar> + * Fixes, ideas, betatesting + * + * "Inaky Perez Gonzalez" <inaky@peloncho.fis.ucm.es> + * Fixes, enhandcements, ideas, betatesting + * + * "Ryuichi Oikawa" <roikawa@rr.iiij4u.or.jp> + * PPC betatesting, PPC support, backward compatibility + * + * "Paul Womar" <Paul@pwomar.demon.co.uk> + * "Owen Waller" <O.Waller@ee.qub.ac.uk> + * PPC betatesting + * + * "Thomas Pornin" <pornin@bolet.ens.fr> + * Alpha betatesting + * + * "Pieter van Leuven" <pvl@iae.nl> + * "Ulf Jaenicke-Roessler" <ujr@physik.phy.tu-dresden.de> + * G100 testing + * + * "H. Peter Arvin" <hpa@transmeta.com> + * Ideas + * + * "Cort Dougan" <cort@cs.nmt.edu> + * CHRP fixes and PReP cleanup + * + * "Mark Vojkovich" <mvojkovi@ucsd.edu> + * G400 support + * + * (following author is not in any relation with this code, but his code + * is included in this driver) + * + * Based on framebuffer driver for VBE 2.0 compliant graphic boards + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + * (following author is not in any relation with this code, but his ideas + * were used when writing this driver) + * + * FreeVBE/AF (Matrox), "Shawn Hargreaves" <shawn@talula.demon.co.uk> + * + */ + +#include <linux/export.h> + +#include "matroxfb_Ti3026.h" +#include "matroxfb_misc.h" +#include "matroxfb_accel.h" +#include <linux/matroxfb.h> + +#ifdef CONFIG_FB_MATROX_MILLENIUM +#define outTi3026 matroxfb_DAC_out +#define inTi3026 matroxfb_DAC_in + +#define TVP3026_INDEX 0x00 +#define TVP3026_PALWRADD 0x00 +#define TVP3026_PALDATA 0x01 +#define TVP3026_PIXRDMSK 0x02 +#define TVP3026_PALRDADD 0x03 +#define TVP3026_CURCOLWRADD 0x04 +#define TVP3026_CLOVERSCAN 0x00 +#define TVP3026_CLCOLOR0 0x01 +#define TVP3026_CLCOLOR1 0x02 +#define TVP3026_CLCOLOR2 0x03 +#define TVP3026_CURCOLDATA 0x05 +#define TVP3026_CURCOLRDADD 0x07 +#define TVP3026_CURCTRL 0x09 +#define TVP3026_X_DATAREG 0x0A +#define TVP3026_CURRAMDATA 0x0B +#define TVP3026_CURPOSXL 0x0C +#define TVP3026_CURPOSXH 0x0D +#define TVP3026_CURPOSYL 0x0E +#define TVP3026_CURPOSYH 0x0F + +#define TVP3026_XSILICONREV 0x01 +#define TVP3026_XCURCTRL 0x06 +#define TVP3026_XCURCTRL_DIS 0x00 /* transparent, transparent, transparent, transparent */ +#define TVP3026_XCURCTRL_3COLOR 0x01 /* transparent, 0, 1, 2 */ +#define TVP3026_XCURCTRL_XGA 0x02 /* 0, 1, transparent, complement */ +#define TVP3026_XCURCTRL_XWIN 0x03 /* transparent, transparent, 0, 1 */ +#define TVP3026_XCURCTRL_BLANK2048 0x00 +#define TVP3026_XCURCTRL_BLANK4096 0x10 +#define TVP3026_XCURCTRL_INTERLACED 0x20 +#define TVP3026_XCURCTRL_ODD 0x00 /* ext.signal ODD/\EVEN */ +#define TVP3026_XCURCTRL_EVEN 0x40 /* ext.signal EVEN/\ODD */ +#define TVP3026_XCURCTRL_INDIRECT 0x00 +#define TVP3026_XCURCTRL_DIRECT 0x80 +#define TVP3026_XLATCHCTRL 0x0F +#define TVP3026_XLATCHCTRL_1_1 0x06 +#define TVP3026_XLATCHCTRL_2_1 0x07 +#define TVP3026_XLATCHCTRL_4_1 0x06 +#define TVP3026_XLATCHCTRL_8_1 0x06 +#define TVP3026_XLATCHCTRL_16_1 0x06 +#define TVP3026A_XLATCHCTRL_4_3 0x06 /* ??? do not understand... but it works... !!! */ +#define TVP3026A_XLATCHCTRL_8_3 0x07 +#define TVP3026B_XLATCHCTRL_4_3 0x08 +#define TVP3026B_XLATCHCTRL_8_3 0x06 /* ??? do not understand... but it works... !!! */ +#define TVP3026_XTRUECOLORCTRL 0x18 +#define TVP3026_XTRUECOLORCTRL_VRAM_SHIFT_ACCEL 0x00 +#define TVP3026_XTRUECOLORCTRL_VRAM_SHIFT_TVP 0x20 +#define TVP3026_XTRUECOLORCTRL_PSEUDOCOLOR 0x80 +#define TVP3026_XTRUECOLORCTRL_TRUECOLOR 0x40 /* paletized */ +#define TVP3026_XTRUECOLORCTRL_DIRECTCOLOR 0x00 +#define TVP3026_XTRUECOLORCTRL_24_ALTERNATE 0x08 /* 5:4/5:2 instead of 4:3/8:3 */ +#define TVP3026_XTRUECOLORCTRL_RGB_888 0x16 /* 4:3/8:3 (or 5:4/5:2) */ +#define TVP3026_XTRUECOLORCTRL_BGR_888 0x17 +#define TVP3026_XTRUECOLORCTRL_ORGB_8888 0x06 +#define TVP3026_XTRUECOLORCTRL_BGRO_8888 0x07 +#define TVP3026_XTRUECOLORCTRL_RGB_565 0x05 +#define TVP3026_XTRUECOLORCTRL_ORGB_1555 0x04 +#define TVP3026_XTRUECOLORCTRL_RGB_664 0x03 +#define TVP3026_XTRUECOLORCTRL_RGBO_4444 0x01 +#define TVP3026_XMUXCTRL 0x19 +#define TVP3026_XMUXCTRL_MEMORY_8BIT 0x01 /* - */ +#define TVP3026_XMUXCTRL_MEMORY_16BIT 0x02 /* - */ +#define TVP3026_XMUXCTRL_MEMORY_32BIT 0x03 /* 2MB RAM, 512K * 4 */ +#define TVP3026_XMUXCTRL_MEMORY_64BIT 0x04 /* >2MB RAM, 512K * 8 & more */ +#define TVP3026_XMUXCTRL_PIXEL_4BIT 0x40 /* L0,H0,L1,H1... */ +#define TVP3026_XMUXCTRL_PIXEL_4BIT_SWAPPED 0x60 /* H0,L0,H1,L1... */ +#define TVP3026_XMUXCTRL_PIXEL_8BIT 0x48 +#define TVP3026_XMUXCTRL_PIXEL_16BIT 0x50 +#define TVP3026_XMUXCTRL_PIXEL_32BIT 0x58 +#define TVP3026_XMUXCTRL_VGA 0x98 /* VGA MEMORY, 8BIT PIXEL */ +#define TVP3026_XCLKCTRL 0x1A +#define TVP3026_XCLKCTRL_DIV1 0x00 +#define TVP3026_XCLKCTRL_DIV2 0x10 +#define TVP3026_XCLKCTRL_DIV4 0x20 +#define TVP3026_XCLKCTRL_DIV8 0x30 +#define TVP3026_XCLKCTRL_DIV16 0x40 +#define TVP3026_XCLKCTRL_DIV32 0x50 +#define TVP3026_XCLKCTRL_DIV64 0x60 +#define TVP3026_XCLKCTRL_CLKSTOPPED 0x70 +#define TVP3026_XCLKCTRL_SRC_CLK0 0x00 +#define TVP3026_XCLKCTRL_SRC_CLK1 0x01 +#define TVP3026_XCLKCTRL_SRC_CLK2 0x02 /* CLK2 is TTL source*/ +#define TVP3026_XCLKCTRL_SRC_NCLK2 0x03 /* not CLK2 is TTL source */ +#define TVP3026_XCLKCTRL_SRC_ECLK2 0x04 /* CLK2 and not CLK2 is ECL source */ +#define TVP3026_XCLKCTRL_SRC_PLL 0x05 +#define TVP3026_XCLKCTRL_SRC_DIS 0x06 /* disable & poweroff internal clock */ +#define TVP3026_XCLKCTRL_SRC_CLK0VGA 0x07 +#define TVP3026_XPALETTEPAGE 0x1C +#define TVP3026_XGENCTRL 0x1D +#define TVP3026_XGENCTRL_HSYNC_POS 0x00 +#define TVP3026_XGENCTRL_HSYNC_NEG 0x01 +#define TVP3026_XGENCTRL_VSYNC_POS 0x00 +#define TVP3026_XGENCTRL_VSYNC_NEG 0x02 +#define TVP3026_XGENCTRL_LITTLE_ENDIAN 0x00 +#define TVP3026_XGENCTRL_BIG_ENDIAN 0x08 +#define TVP3026_XGENCTRL_BLACK_0IRE 0x00 +#define TVP3026_XGENCTRL_BLACK_75IRE 0x10 +#define TVP3026_XGENCTRL_NO_SYNC_ON_GREEN 0x00 +#define TVP3026_XGENCTRL_SYNC_ON_GREEN 0x20 +#define TVP3026_XGENCTRL_OVERSCAN_DIS 0x00 +#define TVP3026_XGENCTRL_OVERSCAN_EN 0x40 +#define TVP3026_XMISCCTRL 0x1E +#define TVP3026_XMISCCTRL_DAC_PUP 0x00 +#define TVP3026_XMISCCTRL_DAC_PDOWN 0x01 +#define TVP3026_XMISCCTRL_DAC_EXT 0x00 /* or 8, bit 3 is ignored */ +#define TVP3026_XMISCCTRL_DAC_6BIT 0x04 +#define TVP3026_XMISCCTRL_DAC_8BIT 0x0C +#define TVP3026_XMISCCTRL_PSEL_DIS 0x00 +#define TVP3026_XMISCCTRL_PSEL_EN 0x10 +#define TVP3026_XMISCCTRL_PSEL_LOW 0x00 /* PSEL high selects directcolor */ +#define TVP3026_XMISCCTRL_PSEL_HIGH 0x20 /* PSEL high selects truecolor or pseudocolor */ +#define TVP3026_XGENIOCTRL 0x2A +#define TVP3026_XGENIODATA 0x2B +#define TVP3026_XPLLADDR 0x2C +#define TVP3026_XPLLADDR_X(LOOP,MCLK,PIX) (((LOOP)<<4) | ((MCLK)<<2) | (PIX)) +#define TVP3026_XPLLDATA_N 0x00 +#define TVP3026_XPLLDATA_M 0x01 +#define TVP3026_XPLLDATA_P 0x02 +#define TVP3026_XPLLDATA_STAT 0x03 +#define TVP3026_XPIXPLLDATA 0x2D +#define TVP3026_XMEMPLLDATA 0x2E +#define TVP3026_XLOOPPLLDATA 0x2F +#define TVP3026_XCOLKEYOVRMIN 0x30 +#define TVP3026_XCOLKEYOVRMAX 0x31 +#define TVP3026_XCOLKEYREDMIN 0x32 +#define TVP3026_XCOLKEYREDMAX 0x33 +#define TVP3026_XCOLKEYGREENMIN 0x34 +#define TVP3026_XCOLKEYGREENMAX 0x35 +#define TVP3026_XCOLKEYBLUEMIN 0x36 +#define TVP3026_XCOLKEYBLUEMAX 0x37 +#define TVP3026_XCOLKEYCTRL 0x38 +#define TVP3026_XCOLKEYCTRL_OVR_EN 0x01 +#define TVP3026_XCOLKEYCTRL_RED_EN 0x02 +#define TVP3026_XCOLKEYCTRL_GREEN_EN 0x04 +#define TVP3026_XCOLKEYCTRL_BLUE_EN 0x08 +#define TVP3026_XCOLKEYCTRL_NEGATE 0x10 +#define TVP3026_XCOLKEYCTRL_ZOOM1 0x00 +#define TVP3026_XCOLKEYCTRL_ZOOM2 0x20 +#define TVP3026_XCOLKEYCTRL_ZOOM4 0x40 +#define TVP3026_XCOLKEYCTRL_ZOOM8 0x60 +#define TVP3026_XCOLKEYCTRL_ZOOM16 0x80 +#define TVP3026_XCOLKEYCTRL_ZOOM32 0xA0 +#define TVP3026_XMEMPLLCTRL 0x39 +#define TVP3026_XMEMPLLCTRL_DIV(X) (((X)-1)>>1) /* 2,4,6,8,10,12,14,16, division applied to LOOP PLL after divide by 2^P */ +#define TVP3026_XMEMPLLCTRL_STROBEMKC4 0x08 +#define TVP3026_XMEMPLLCTRL_MCLK_DOTCLOCK 0x00 /* MKC4 */ +#define TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL 0x10 /* MKC4 */ +#define TVP3026_XMEMPLLCTRL_RCLK_PIXPLL 0x00 +#define TVP3026_XMEMPLLCTRL_RCLK_LOOPPLL 0x20 +#define TVP3026_XMEMPLLCTRL_RCLK_DOTDIVN 0x40 /* dot clock divided by loop pclk N prescaler */ +#define TVP3026_XSENSETEST 0x3A +#define TVP3026_XTESTMODEDATA 0x3B +#define TVP3026_XCRCREML 0x3C +#define TVP3026_XCRCREMH 0x3D +#define TVP3026_XCRCBITSEL 0x3E +#define TVP3026_XID 0x3F + +static const unsigned char DACseq[] = +{ TVP3026_XLATCHCTRL, TVP3026_XTRUECOLORCTRL, + TVP3026_XMUXCTRL, TVP3026_XCLKCTRL, + TVP3026_XPALETTEPAGE, + TVP3026_XGENCTRL, + TVP3026_XMISCCTRL, + TVP3026_XGENIOCTRL, + TVP3026_XGENIODATA, + TVP3026_XCOLKEYOVRMIN, TVP3026_XCOLKEYOVRMAX, TVP3026_XCOLKEYREDMIN, TVP3026_XCOLKEYREDMAX, + TVP3026_XCOLKEYGREENMIN, TVP3026_XCOLKEYGREENMAX, TVP3026_XCOLKEYBLUEMIN, TVP3026_XCOLKEYBLUEMAX, + TVP3026_XCOLKEYCTRL, + TVP3026_XMEMPLLCTRL, TVP3026_XSENSETEST, TVP3026_XCURCTRL }; + +#define POS3026_XLATCHCTRL 0 +#define POS3026_XTRUECOLORCTRL 1 +#define POS3026_XMUXCTRL 2 +#define POS3026_XCLKCTRL 3 +#define POS3026_XGENCTRL 5 +#define POS3026_XMISCCTRL 6 +#define POS3026_XMEMPLLCTRL 18 +#define POS3026_XCURCTRL 20 + +static const unsigned char MGADACbpp32[] = +{ TVP3026_XLATCHCTRL_2_1, TVP3026_XTRUECOLORCTRL_DIRECTCOLOR | TVP3026_XTRUECOLORCTRL_ORGB_8888, + 0x00, TVP3026_XCLKCTRL_DIV1 | TVP3026_XCLKCTRL_SRC_PLL, + 0x00, + TVP3026_XGENCTRL_HSYNC_POS | TVP3026_XGENCTRL_VSYNC_POS | TVP3026_XGENCTRL_LITTLE_ENDIAN | TVP3026_XGENCTRL_BLACK_0IRE | TVP3026_XGENCTRL_NO_SYNC_ON_GREEN | TVP3026_XGENCTRL_OVERSCAN_DIS, + TVP3026_XMISCCTRL_DAC_PUP | TVP3026_XMISCCTRL_DAC_8BIT | TVP3026_XMISCCTRL_PSEL_DIS | TVP3026_XMISCCTRL_PSEL_HIGH, + 0x00, + 0x1E, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + TVP3026_XCOLKEYCTRL_ZOOM1, + 0x00, 0x00, TVP3026_XCURCTRL_DIS }; + +static int Ti3026_calcclock(const struct matrox_fb_info *minfo, + unsigned int freq, unsigned int fmax, int *in, + int *feed, int *post) +{ + unsigned int fvco; + unsigned int lin, lfeed, lpost; + + DBG(__func__) + + fvco = PLL_calcclock(minfo, freq, fmax, &lin, &lfeed, &lpost); + fvco >>= (*post = lpost); + *in = 64 - lin; + *feed = 64 - lfeed; + return fvco; +} + +static int Ti3026_setpclk(struct matrox_fb_info *minfo, int clk) +{ + unsigned int f_pll; + unsigned int pixfeed, pixin, pixpost; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + f_pll = Ti3026_calcclock(minfo, clk, minfo->max_pixel_clock, &pixin, &pixfeed, &pixpost); + + hw->DACclk[0] = pixin | 0xC0; + hw->DACclk[1] = pixfeed; + hw->DACclk[2] = pixpost | 0xB0; + + { + unsigned int loopfeed, loopin, looppost, loopdiv, z; + unsigned int Bpp; + + Bpp = minfo->curr.final_bppShift; + + if (minfo->fbcon.var.bits_per_pixel == 24) { + loopfeed = 3; /* set lm to any possible value */ + loopin = 3 * 32 / Bpp; + } else { + loopfeed = 4; + loopin = 4 * 32 / Bpp; + } + z = (110000 * loopin) / (f_pll * loopfeed); + loopdiv = 0; /* div 2 */ + if (z < 2) + looppost = 0; + else if (z < 4) + looppost = 1; + else if (z < 8) + looppost = 2; + else { + looppost = 3; + loopdiv = z/16; + } + if (minfo->fbcon.var.bits_per_pixel == 24) { + hw->DACclk[3] = ((65 - loopin) & 0x3F) | 0xC0; + hw->DACclk[4] = (65 - loopfeed) | 0x80; + if (minfo->accel.ramdac_rev > 0x20) { + if (isInterleave(minfo)) + hw->DACreg[POS3026_XLATCHCTRL] = TVP3026B_XLATCHCTRL_8_3; + else { + hw->DACclk[4] &= ~0xC0; + hw->DACreg[POS3026_XLATCHCTRL] = TVP3026B_XLATCHCTRL_4_3; + } + } else { + if (isInterleave(minfo)) + ; /* default... */ + else { + hw->DACclk[4] ^= 0xC0; /* change from 0x80 to 0x40 */ + hw->DACreg[POS3026_XLATCHCTRL] = TVP3026A_XLATCHCTRL_4_3; + } + } + hw->DACclk[5] = looppost | 0xF8; + if (minfo->devflags.mga_24bpp_fix) + hw->DACclk[5] ^= 0x40; + } else { + hw->DACclk[3] = ((65 - loopin) & 0x3F) | 0xC0; + hw->DACclk[4] = 65 - loopfeed; + hw->DACclk[5] = looppost | 0xF0; + } + hw->DACreg[POS3026_XMEMPLLCTRL] = loopdiv | TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL | TVP3026_XMEMPLLCTRL_RCLK_LOOPPLL; + } + return 0; +} + +static int Ti3026_init(struct matrox_fb_info *minfo, struct my_timming *m) +{ + u_int8_t muxctrl = isInterleave(minfo) ? TVP3026_XMUXCTRL_MEMORY_64BIT : TVP3026_XMUXCTRL_MEMORY_32BIT; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + memcpy(hw->DACreg, MGADACbpp32, sizeof(MGADACbpp32)); + switch (minfo->fbcon.var.bits_per_pixel) { + case 4: hw->DACreg[POS3026_XLATCHCTRL] = TVP3026_XLATCHCTRL_16_1; /* or _8_1, they are same */ + hw->DACreg[POS3026_XTRUECOLORCTRL] = TVP3026_XTRUECOLORCTRL_PSEUDOCOLOR; + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_4BIT; + hw->DACreg[POS3026_XCLKCTRL] = TVP3026_XCLKCTRL_SRC_PLL | TVP3026_XCLKCTRL_DIV8; + hw->DACreg[POS3026_XMISCCTRL] = TVP3026_XMISCCTRL_DAC_PUP | TVP3026_XMISCCTRL_DAC_8BIT | TVP3026_XMISCCTRL_PSEL_DIS | TVP3026_XMISCCTRL_PSEL_LOW; + break; + case 8: hw->DACreg[POS3026_XLATCHCTRL] = TVP3026_XLATCHCTRL_8_1; /* or _4_1, they are same */ + hw->DACreg[POS3026_XTRUECOLORCTRL] = TVP3026_XTRUECOLORCTRL_PSEUDOCOLOR; + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_8BIT; + hw->DACreg[POS3026_XCLKCTRL] = TVP3026_XCLKCTRL_SRC_PLL | TVP3026_XCLKCTRL_DIV4; + hw->DACreg[POS3026_XMISCCTRL] = TVP3026_XMISCCTRL_DAC_PUP | TVP3026_XMISCCTRL_DAC_8BIT | TVP3026_XMISCCTRL_PSEL_DIS | TVP3026_XMISCCTRL_PSEL_LOW; + break; + case 16: + /* XLATCHCTRL should be _4_1 / _2_1... Why is not? (_2_1 is used every time) */ + hw->DACreg[POS3026_XTRUECOLORCTRL] = (minfo->fbcon.var.green.length == 5) ? (TVP3026_XTRUECOLORCTRL_DIRECTCOLOR | TVP3026_XTRUECOLORCTRL_ORGB_1555) : (TVP3026_XTRUECOLORCTRL_DIRECTCOLOR | TVP3026_XTRUECOLORCTRL_RGB_565); + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_16BIT; + hw->DACreg[POS3026_XCLKCTRL] = TVP3026_XCLKCTRL_SRC_PLL | TVP3026_XCLKCTRL_DIV2; + break; + case 24: + /* XLATCHCTRL is: for (A) use _4_3 (?_8_3 is same? TBD), for (B) it is set in setpclk */ + hw->DACreg[POS3026_XTRUECOLORCTRL] = TVP3026_XTRUECOLORCTRL_DIRECTCOLOR | TVP3026_XTRUECOLORCTRL_RGB_888; + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_32BIT; + hw->DACreg[POS3026_XCLKCTRL] = TVP3026_XCLKCTRL_SRC_PLL | TVP3026_XCLKCTRL_DIV4; + break; + case 32: + /* XLATCHCTRL should be _2_1 / _1_1... Why is not? (_2_1 is used every time) */ + hw->DACreg[POS3026_XMUXCTRL] = muxctrl | TVP3026_XMUXCTRL_PIXEL_32BIT; + break; + default: + return 1; /* TODO: failed */ + } + if (matroxfb_vgaHWinit(minfo, m)) return 1; + + /* set SYNC */ + hw->MiscOutReg = 0xCB; + if (m->sync & FB_SYNC_HOR_HIGH_ACT) + hw->DACreg[POS3026_XGENCTRL] |= TVP3026_XGENCTRL_HSYNC_NEG; + if (m->sync & FB_SYNC_VERT_HIGH_ACT) + hw->DACreg[POS3026_XGENCTRL] |= TVP3026_XGENCTRL_VSYNC_NEG; + if (m->sync & FB_SYNC_ON_GREEN) + hw->DACreg[POS3026_XGENCTRL] |= TVP3026_XGENCTRL_SYNC_ON_GREEN; + + /* set DELAY */ + if (minfo->video.len < 0x400000) + hw->CRTCEXT[3] |= 0x08; + else if (minfo->video.len > 0x400000) + hw->CRTCEXT[3] |= 0x10; + + /* set HWCURSOR */ + if (m->interlaced) { + hw->DACreg[POS3026_XCURCTRL] |= TVP3026_XCURCTRL_INTERLACED; + } + if (m->HTotal >= 1536) + hw->DACreg[POS3026_XCURCTRL] |= TVP3026_XCURCTRL_BLANK4096; + + /* set interleaving */ + hw->MXoptionReg &= ~0x00001000; + if (isInterleave(minfo)) hw->MXoptionReg |= 0x00001000; + + /* set DAC */ + Ti3026_setpclk(minfo, m->pixclock); + return 0; +} + +static void ti3026_setMCLK(struct matrox_fb_info *minfo, int fout) +{ + unsigned int f_pll; + unsigned int pclk_m, pclk_n, pclk_p; + unsigned int mclk_m, mclk_n, mclk_p; + unsigned int rfhcnt, mclk_ctl; + int tmout; + + DBG(__func__) + + f_pll = Ti3026_calcclock(minfo, fout, minfo->max_pixel_clock, &mclk_n, &mclk_m, &mclk_p); + + /* save pclk */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFC); + pclk_n = inTi3026(minfo, TVP3026_XPIXPLLDATA); + outTi3026(minfo, TVP3026_XPLLADDR, 0xFD); + pclk_m = inTi3026(minfo, TVP3026_XPIXPLLDATA); + outTi3026(minfo, TVP3026_XPLLADDR, 0xFE); + pclk_p = inTi3026(minfo, TVP3026_XPIXPLLDATA); + + /* stop pclk */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFE); + outTi3026(minfo, TVP3026_XPIXPLLDATA, 0x00); + + /* set pclk to new mclk */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFC); + outTi3026(minfo, TVP3026_XPIXPLLDATA, mclk_n | 0xC0); + outTi3026(minfo, TVP3026_XPIXPLLDATA, mclk_m); + outTi3026(minfo, TVP3026_XPIXPLLDATA, mclk_p | 0xB0); + + /* wait for PLL to lock */ + for (tmout = 500000; tmout; tmout--) { + if (inTi3026(minfo, TVP3026_XPIXPLLDATA) & 0x40) + break; + udelay(10); + } + if (!tmout) + printk(KERN_ERR "matroxfb: Temporary pixel PLL not locked after 5 secs\n"); + + /* output pclk on mclk pin */ + mclk_ctl = inTi3026(minfo, TVP3026_XMEMPLLCTRL); + outTi3026(minfo, TVP3026_XMEMPLLCTRL, mclk_ctl & 0xE7); + outTi3026(minfo, TVP3026_XMEMPLLCTRL, (mclk_ctl & 0xE7) | TVP3026_XMEMPLLCTRL_STROBEMKC4); + + /* stop MCLK */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFB); + outTi3026(minfo, TVP3026_XMEMPLLDATA, 0x00); + + /* set mclk to new freq */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xF3); + outTi3026(minfo, TVP3026_XMEMPLLDATA, mclk_n | 0xC0); + outTi3026(minfo, TVP3026_XMEMPLLDATA, mclk_m); + outTi3026(minfo, TVP3026_XMEMPLLDATA, mclk_p | 0xB0); + + /* wait for PLL to lock */ + for (tmout = 500000; tmout; tmout--) { + if (inTi3026(minfo, TVP3026_XMEMPLLDATA) & 0x40) + break; + udelay(10); + } + if (!tmout) + printk(KERN_ERR "matroxfb: Memory PLL not locked after 5 secs\n"); + + f_pll = f_pll * 333 / (10000 << mclk_p); + if (isMilleniumII(minfo)) { + rfhcnt = (f_pll - 128) / 256; + if (rfhcnt > 15) + rfhcnt = 15; + } else { + rfhcnt = (f_pll - 64) / 128; + if (rfhcnt > 15) + rfhcnt = 0; + } + minfo->hw.MXoptionReg = (minfo->hw.MXoptionReg & ~0x000F0000) | (rfhcnt << 16); + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, minfo->hw.MXoptionReg); + + /* output MCLK to MCLK pin */ + outTi3026(minfo, TVP3026_XMEMPLLCTRL, (mclk_ctl & 0xE7) | TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL); + outTi3026(minfo, TVP3026_XMEMPLLCTRL, (mclk_ctl ) | TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL | TVP3026_XMEMPLLCTRL_STROBEMKC4); + + /* stop PCLK */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFE); + outTi3026(minfo, TVP3026_XPIXPLLDATA, 0x00); + + /* restore pclk */ + outTi3026(minfo, TVP3026_XPLLADDR, 0xFC); + outTi3026(minfo, TVP3026_XPIXPLLDATA, pclk_n); + outTi3026(minfo, TVP3026_XPIXPLLDATA, pclk_m); + outTi3026(minfo, TVP3026_XPIXPLLDATA, pclk_p); + + /* wait for PLL to lock */ + for (tmout = 500000; tmout; tmout--) { + if (inTi3026(minfo, TVP3026_XPIXPLLDATA) & 0x40) + break; + udelay(10); + } + if (!tmout) + printk(KERN_ERR "matroxfb: Pixel PLL not locked after 5 secs\n"); +} + +static void ti3026_ramdac_init(struct matrox_fb_info *minfo) +{ + DBG(__func__) + + minfo->features.pll.vco_freq_min = 110000; + minfo->features.pll.ref_freq = 114545; + minfo->features.pll.feed_div_min = 2; + minfo->features.pll.feed_div_max = 24; + minfo->features.pll.in_div_min = 2; + minfo->features.pll.in_div_max = 63; + minfo->features.pll.post_shift_max = 3; + if (minfo->devflags.noinit) + return; + ti3026_setMCLK(minfo, 60000); +} + +static void Ti3026_restore(struct matrox_fb_info *minfo) +{ + int i; + unsigned char progdac[6]; + struct matrox_hw_state *hw = &minfo->hw; + CRITFLAGS + + DBG(__func__) + +#ifdef DEBUG + dprintk(KERN_INFO "EXTVGA regs: "); + for (i = 0; i < 6; i++) + dprintk("%02X:", hw->CRTCEXT[i]); + dprintk("\n"); +#endif + + CRITBEGIN + + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + + CRITEND + + matroxfb_vgaHWrestore(minfo); + + CRITBEGIN + + minfo->crtc1.panpos = -1; + for (i = 0; i < 6; i++) + mga_setr(M_EXTVGA_INDEX, i, hw->CRTCEXT[i]); + + for (i = 0; i < 21; i++) { + outTi3026(minfo, DACseq[i], hw->DACreg[i]); + } + + outTi3026(minfo, TVP3026_XPLLADDR, 0x00); + progdac[0] = inTi3026(minfo, TVP3026_XPIXPLLDATA); + progdac[3] = inTi3026(minfo, TVP3026_XLOOPPLLDATA); + outTi3026(minfo, TVP3026_XPLLADDR, 0x15); + progdac[1] = inTi3026(minfo, TVP3026_XPIXPLLDATA); + progdac[4] = inTi3026(minfo, TVP3026_XLOOPPLLDATA); + outTi3026(minfo, TVP3026_XPLLADDR, 0x2A); + progdac[2] = inTi3026(minfo, TVP3026_XPIXPLLDATA); + progdac[5] = inTi3026(minfo, TVP3026_XLOOPPLLDATA); + + CRITEND + if (memcmp(hw->DACclk, progdac, 6)) { + /* agrhh... setting up PLL is very slow on Millennium... */ + /* Mystique PLL is locked in few ms, but Millennium PLL lock takes about 0.15 s... */ + /* Maybe even we should call schedule() ? */ + + CRITBEGIN + outTi3026(minfo, TVP3026_XCLKCTRL, hw->DACreg[POS3026_XCLKCTRL]); + outTi3026(minfo, TVP3026_XPLLADDR, 0x2A); + outTi3026(minfo, TVP3026_XLOOPPLLDATA, 0); + outTi3026(minfo, TVP3026_XPIXPLLDATA, 0); + + outTi3026(minfo, TVP3026_XPLLADDR, 0x00); + for (i = 0; i < 3; i++) + outTi3026(minfo, TVP3026_XPIXPLLDATA, hw->DACclk[i]); + /* wait for PLL only if PLL clock requested (always for PowerMode, never for VGA) */ + if (hw->MiscOutReg & 0x08) { + int tmout; + outTi3026(minfo, TVP3026_XPLLADDR, 0x3F); + for (tmout = 500000; tmout; --tmout) { + if (inTi3026(minfo, TVP3026_XPIXPLLDATA) & 0x40) + break; + udelay(10); + } + + CRITEND + + if (!tmout) + printk(KERN_ERR "matroxfb: Pixel PLL not locked after 5 secs\n"); + else + dprintk(KERN_INFO "PixelPLL: %d\n", 500000-tmout); + CRITBEGIN + } + outTi3026(minfo, TVP3026_XMEMPLLCTRL, hw->DACreg[POS3026_XMEMPLLCTRL]); + outTi3026(minfo, TVP3026_XPLLADDR, 0x00); + for (i = 3; i < 6; i++) + outTi3026(minfo, TVP3026_XLOOPPLLDATA, hw->DACclk[i]); + CRITEND + if ((hw->MiscOutReg & 0x08) && ((hw->DACclk[5] & 0x80) == 0x80)) { + int tmout; + + CRITBEGIN + outTi3026(minfo, TVP3026_XPLLADDR, 0x3F); + for (tmout = 500000; tmout; --tmout) { + if (inTi3026(minfo, TVP3026_XLOOPPLLDATA) & 0x40) + break; + udelay(10); + } + CRITEND + if (!tmout) + printk(KERN_ERR "matroxfb: Loop PLL not locked after 5 secs\n"); + else + dprintk(KERN_INFO "LoopPLL: %d\n", 500000-tmout); + } + } + +#ifdef DEBUG + dprintk(KERN_DEBUG "3026DACregs "); + for (i = 0; i < 21; i++) { + dprintk("R%02X=%02X ", DACseq[i], hw->DACreg[i]); + if ((i & 0x7) == 0x7) dprintk(KERN_DEBUG "continuing... "); + } + dprintk(KERN_DEBUG "DACclk "); + for (i = 0; i < 6; i++) + dprintk("C%02X=%02X ", i, hw->DACclk[i]); + dprintk("\n"); +#endif +} + +static void Ti3026_reset(struct matrox_fb_info *minfo) +{ + DBG(__func__) + + ti3026_ramdac_init(minfo); +} + +static struct matrox_altout ti3026_output = { + .name = "Primary output", +}; + +static int Ti3026_preinit(struct matrox_fb_info *minfo) +{ + static const int vxres_mill2[] = { 512, 640, 768, 800, 832, 960, + 1024, 1152, 1280, 1600, 1664, 1920, + 2048, 0}; + static const int vxres_mill1[] = { 640, 768, 800, 960, + 1024, 1152, 1280, 1600, 1920, + 2048, 0}; + struct matrox_hw_state *hw = &minfo->hw; + + DBG(__func__) + + minfo->millenium = 1; + minfo->milleniumII = (minfo->pcidev->device != PCI_DEVICE_ID_MATROX_MIL); + minfo->capable.cfb4 = 1; + minfo->capable.text = 1; /* isMilleniumII(minfo); */ + minfo->capable.vxres = isMilleniumII(minfo) ? vxres_mill2 : vxres_mill1; + + minfo->outputs[0].data = minfo; + minfo->outputs[0].output = &ti3026_output; + minfo->outputs[0].src = minfo->outputs[0].default_src; + minfo->outputs[0].mode = MATROXFB_OUTPUT_MODE_MONITOR; + + if (minfo->devflags.noinit) + return 0; + /* preserve VGA I/O, BIOS and PPC */ + hw->MXoptionReg &= 0xC0000100; + hw->MXoptionReg |= 0x002C0000; + if (minfo->devflags.novga) + hw->MXoptionReg &= ~0x00000100; + if (minfo->devflags.nobios) + hw->MXoptionReg &= ~0x40000000; + if (minfo->devflags.nopciretry) + hw->MXoptionReg |= 0x20000000; + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, hw->MXoptionReg); + + minfo->accel.ramdac_rev = inTi3026(minfo, TVP3026_XSILICONREV); + + outTi3026(minfo, TVP3026_XCLKCTRL, TVP3026_XCLKCTRL_SRC_CLK0VGA | TVP3026_XCLKCTRL_CLKSTOPPED); + outTi3026(minfo, TVP3026_XTRUECOLORCTRL, TVP3026_XTRUECOLORCTRL_PSEUDOCOLOR); + outTi3026(minfo, TVP3026_XMUXCTRL, TVP3026_XMUXCTRL_VGA); + + outTi3026(minfo, TVP3026_XPLLADDR, 0x2A); + outTi3026(minfo, TVP3026_XLOOPPLLDATA, 0x00); + outTi3026(minfo, TVP3026_XPIXPLLDATA, 0x00); + + mga_outb(M_MISC_REG, 0x67); + + outTi3026(minfo, TVP3026_XMEMPLLCTRL, TVP3026_XMEMPLLCTRL_STROBEMKC4 | TVP3026_XMEMPLLCTRL_MCLK_MCLKPLL); + + mga_outl(M_RESET, 1); + udelay(250); + mga_outl(M_RESET, 0); + udelay(250); + mga_outl(M_MACCESS, 0x00008000); + udelay(10); + return 0; +} + +struct matrox_switch matrox_millennium = { + .preinit = Ti3026_preinit, + .reset = Ti3026_reset, + .init = Ti3026_init, + .restore = Ti3026_restore +}; +EXPORT_SYMBOL(matrox_millennium); +#endif +MODULE_DESCRIPTION("Matrox Millennium output driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_Ti3026.h b/drivers/video/fbdev/matrox/matroxfb_Ti3026.h new file mode 100644 index 000000000..faee149d0 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_Ti3026.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MATROXFB_TI3026_H__ +#define __MATROXFB_TI3026_H__ + + +#include "matroxfb_base.h" + +#ifdef CONFIG_FB_MATROX_MILLENIUM +extern struct matrox_switch matrox_millennium; +#endif + +#endif /* __MATROXFB_TI3026_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_accel.c b/drivers/video/fbdev/matrox/matroxfb_accel.c new file mode 100644 index 000000000..2670db392 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_accel.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200 and G400 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Version: 1.65 2002/08/14 + * + * MTRR stuff: 1998 Tom Rini <trini@kernel.crashing.org> + * + * Contributors: "menion?" <menion@mindless.com> + * Betatesting, fixes, ideas + * + * "Kurt Garloff" <garloff@suse.de> + * Betatesting, fixes, ideas, videomodes, videomodes timmings + * + * "Tom Rini" <trini@kernel.crashing.org> + * MTRR stuff, PPC cleanups, betatesting, fixes, ideas + * + * "Bibek Sahu" <scorpio@dodds.net> + * Access device through readb|w|l and write b|w|l + * Extensive debugging stuff + * + * "Daniel Haun" <haund@usa.net> + * Testing, hardware cursor fixes + * + * "Scott Wood" <sawst46+@pitt.edu> + * Fixes + * + * "Gerd Knorr" <kraxel@goldbach.isdn.cs.tu-berlin.de> + * Betatesting + * + * "Kelly French" <targon@hazmat.com> + * "Fernando Herrera" <fherrera@eurielec.etsit.upm.es> + * Betatesting, bug reporting + * + * "Pablo Bianucci" <pbian@pccp.com.ar> + * Fixes, ideas, betatesting + * + * "Inaky Perez Gonzalez" <inaky@peloncho.fis.ucm.es> + * Fixes, enhandcements, ideas, betatesting + * + * "Ryuichi Oikawa" <roikawa@rr.iiij4u.or.jp> + * PPC betatesting, PPC support, backward compatibility + * + * "Paul Womar" <Paul@pwomar.demon.co.uk> + * "Owen Waller" <O.Waller@ee.qub.ac.uk> + * PPC betatesting + * + * "Thomas Pornin" <pornin@bolet.ens.fr> + * Alpha betatesting + * + * "Pieter van Leuven" <pvl@iae.nl> + * "Ulf Jaenicke-Roessler" <ujr@physik.phy.tu-dresden.de> + * G100 testing + * + * "H. Peter Arvin" <hpa@transmeta.com> + * Ideas + * + * "Cort Dougan" <cort@cs.nmt.edu> + * CHRP fixes and PReP cleanup + * + * "Mark Vojkovich" <mvojkovi@ucsd.edu> + * G400 support + * + * (following author is not in any relation with this code, but his code + * is included in this driver) + * + * Based on framebuffer driver for VBE 2.0 compliant graphic boards + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + * (following author is not in any relation with this code, but his ideas + * were used when writing this driver) + * + * FreeVBE/AF (Matrox), "Shawn Hargreaves" <shawn@talula.demon.co.uk> + * + */ + +#include <linux/export.h> + +#include "matroxfb_accel.h" +#include "matroxfb_DAC1064.h" +#include "matroxfb_Ti3026.h" +#include "matroxfb_misc.h" + +#define curr_ydstorg(x) ((x)->curr.ydstorg.pixels) + +#define mga_ydstlen(y,l) mga_outl(M_YDSTLEN | M_EXEC, ((y) << 16) | (l)) + +static inline void matrox_cfb4_pal(u_int32_t* pal) { + unsigned int i; + + for (i = 0; i < 16; i++) { + pal[i] = i * 0x11111111U; + } +} + +static inline void matrox_cfb8_pal(u_int32_t* pal) { + unsigned int i; + + for (i = 0; i < 16; i++) { + pal[i] = i * 0x01010101U; + } +} + +static void matroxfb_copyarea(struct fb_info* info, const struct fb_copyarea* area); +static void matroxfb_fillrect(struct fb_info* info, const struct fb_fillrect* rect); +static void matroxfb_imageblit(struct fb_info* info, const struct fb_image* image); +static void matroxfb_cfb4_fillrect(struct fb_info* info, const struct fb_fillrect* rect); +static void matroxfb_cfb4_copyarea(struct fb_info* info, const struct fb_copyarea* area); + +void matrox_cfbX_init(struct matrox_fb_info *minfo) +{ + u_int32_t maccess; + u_int32_t mpitch; + u_int32_t mopmode; + int accel; + + DBG(__func__) + + mpitch = minfo->fbcon.var.xres_virtual; + + minfo->fbops.fb_copyarea = cfb_copyarea; + minfo->fbops.fb_fillrect = cfb_fillrect; + minfo->fbops.fb_imageblit = cfb_imageblit; + minfo->fbops.fb_cursor = NULL; + + accel = (minfo->fbcon.var.accel_flags & FB_ACCELF_TEXT) == FB_ACCELF_TEXT; + + switch (minfo->fbcon.var.bits_per_pixel) { + case 4: maccess = 0x00000000; /* accelerate as 8bpp video */ + mpitch = (mpitch >> 1) | 0x8000; /* disable linearization */ + mopmode = M_OPMODE_4BPP; + matrox_cfb4_pal(minfo->cmap); + if (accel && !(mpitch & 1)) { + minfo->fbops.fb_copyarea = matroxfb_cfb4_copyarea; + minfo->fbops.fb_fillrect = matroxfb_cfb4_fillrect; + } + break; + case 8: maccess = 0x00000000; + mopmode = M_OPMODE_8BPP; + matrox_cfb8_pal(minfo->cmap); + if (accel) { + minfo->fbops.fb_copyarea = matroxfb_copyarea; + minfo->fbops.fb_fillrect = matroxfb_fillrect; + minfo->fbops.fb_imageblit = matroxfb_imageblit; + } + break; + case 16: if (minfo->fbcon.var.green.length == 5) + maccess = 0xC0000001; + else + maccess = 0x40000001; + mopmode = M_OPMODE_16BPP; + if (accel) { + minfo->fbops.fb_copyarea = matroxfb_copyarea; + minfo->fbops.fb_fillrect = matroxfb_fillrect; + minfo->fbops.fb_imageblit = matroxfb_imageblit; + } + break; + case 24: maccess = 0x00000003; + mopmode = M_OPMODE_24BPP; + if (accel) { + minfo->fbops.fb_copyarea = matroxfb_copyarea; + minfo->fbops.fb_fillrect = matroxfb_fillrect; + minfo->fbops.fb_imageblit = matroxfb_imageblit; + } + break; + case 32: maccess = 0x00000002; + mopmode = M_OPMODE_32BPP; + if (accel) { + minfo->fbops.fb_copyarea = matroxfb_copyarea; + minfo->fbops.fb_fillrect = matroxfb_fillrect; + minfo->fbops.fb_imageblit = matroxfb_imageblit; + } + break; + default: maccess = 0x00000000; + mopmode = 0x00000000; + break; /* turn off acceleration!!! */ + } + mga_fifo(8); + mga_outl(M_PITCH, mpitch); + mga_outl(M_YDSTORG, curr_ydstorg(minfo)); + if (minfo->capable.plnwt) + mga_outl(M_PLNWT, -1); + if (minfo->capable.srcorg) { + mga_outl(M_SRCORG, 0); + mga_outl(M_DSTORG, 0); + } + mga_outl(M_OPMODE, mopmode); + mga_outl(M_CXBNDRY, 0xFFFF0000); + mga_outl(M_YTOP, 0); + mga_outl(M_YBOT, 0x01FFFFFF); + mga_outl(M_MACCESS, maccess); + minfo->accel.m_dwg_rect = M_DWG_TRAP | M_DWG_SOLID | M_DWG_ARZERO | M_DWG_SGNZERO | M_DWG_SHIFTZERO; + if (isMilleniumII(minfo)) minfo->accel.m_dwg_rect |= M_DWG_TRANSC; + minfo->accel.m_opmode = mopmode; + minfo->accel.m_access = maccess; + minfo->accel.m_pitch = mpitch; +} + +EXPORT_SYMBOL(matrox_cfbX_init); + +static void matrox_accel_restore_maccess(struct matrox_fb_info *minfo) +{ + mga_outl(M_MACCESS, minfo->accel.m_access); + mga_outl(M_PITCH, minfo->accel.m_pitch); +} + +static void matrox_accel_bmove(struct matrox_fb_info *minfo, int vxres, int sy, + int sx, int dy, int dx, int height, int width) +{ + int start, end; + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + if ((dy < sy) || ((dy == sy) && (dx <= sx))) { + mga_fifo(4); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_SGNZERO | + M_DWG_BFCOL | M_DWG_REPLACE); + mga_outl(M_AR5, vxres); + width--; + start = sy*vxres+sx+curr_ydstorg(minfo); + end = start+width; + } else { + mga_fifo(5); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_BFCOL | M_DWG_REPLACE); + mga_outl(M_SGN, 5); + mga_outl(M_AR5, -vxres); + width--; + end = (sy+height-1)*vxres+sx+curr_ydstorg(minfo); + start = end+width; + dy += height-1; + } + mga_fifo(6); + matrox_accel_restore_maccess(minfo); + mga_outl(M_AR0, end); + mga_outl(M_AR3, start); + mga_outl(M_FXBNDRY, ((dx+width)<<16) | dx); + mga_ydstlen(dy, height); + WaitTillIdle(); + + CRITEND +} + +static void matrox_accel_bmove_lin(struct matrox_fb_info *minfo, int vxres, + int sy, int sx, int dy, int dx, int height, + int width) +{ + int start, end; + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + if ((dy < sy) || ((dy == sy) && (dx <= sx))) { + mga_fifo(4); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_SGNZERO | + M_DWG_BFCOL | M_DWG_REPLACE); + mga_outl(M_AR5, vxres); + width--; + start = sy*vxres+sx+curr_ydstorg(minfo); + end = start+width; + } else { + mga_fifo(5); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, M_DWG_BITBLT | M_DWG_SHIFTZERO | M_DWG_BFCOL | M_DWG_REPLACE); + mga_outl(M_SGN, 5); + mga_outl(M_AR5, -vxres); + width--; + end = (sy+height-1)*vxres+sx+curr_ydstorg(minfo); + start = end+width; + dy += height-1; + } + mga_fifo(7); + matrox_accel_restore_maccess(minfo); + mga_outl(M_AR0, end); + mga_outl(M_AR3, start); + mga_outl(M_FXBNDRY, ((dx+width)<<16) | dx); + mga_outl(M_YDST, dy*vxres >> 5); + mga_outl(M_LEN | M_EXEC, height); + WaitTillIdle(); + + CRITEND +} + +static void matroxfb_cfb4_copyarea(struct fb_info* info, const struct fb_copyarea* area) { + struct matrox_fb_info *minfo = info2minfo(info); + + if ((area->sx | area->dx | area->width) & 1) + cfb_copyarea(info, area); + else + matrox_accel_bmove_lin(minfo, minfo->fbcon.var.xres_virtual >> 1, area->sy, area->sx >> 1, area->dy, area->dx >> 1, area->height, area->width >> 1); +} + +static void matroxfb_copyarea(struct fb_info* info, const struct fb_copyarea* area) { + struct matrox_fb_info *minfo = info2minfo(info); + + matrox_accel_bmove(minfo, minfo->fbcon.var.xres_virtual, area->sy, area->sx, area->dy, area->dx, area->height, area->width); +} + +static void matroxfb_accel_clear(struct matrox_fb_info *minfo, u_int32_t color, + int sy, int sx, int height, int width) +{ + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + mga_fifo(7); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, minfo->accel.m_dwg_rect | M_DWG_REPLACE); + mga_outl(M_FCOL, color); + mga_outl(M_FXBNDRY, ((sx + width) << 16) | sx); + mga_ydstlen(sy, height); + WaitTillIdle(); + + CRITEND +} + +static void matroxfb_fillrect(struct fb_info* info, const struct fb_fillrect* rect) { + struct matrox_fb_info *minfo = info2minfo(info); + + switch (rect->rop) { + case ROP_COPY: + matroxfb_accel_clear(minfo, ((u_int32_t *)info->pseudo_palette)[rect->color], rect->dy, rect->dx, rect->height, rect->width); + break; + } +} + +static void matroxfb_cfb4_clear(struct matrox_fb_info *minfo, u_int32_t bgx, + int sy, int sx, int height, int width) +{ + int whattodo; + CRITFLAGS + + DBG(__func__) + + CRITBEGIN + + whattodo = 0; + if (sx & 1) { + sx ++; + if (!width) return; + width --; + whattodo = 1; + } + if (width & 1) { + whattodo |= 2; + } + width >>= 1; + sx >>= 1; + if (width) { + mga_fifo(7); + matrox_accel_restore_maccess(minfo); + mga_outl(M_DWGCTL, minfo->accel.m_dwg_rect | M_DWG_REPLACE2); + mga_outl(M_FCOL, bgx); + mga_outl(M_FXBNDRY, ((sx + width) << 16) | sx); + mga_outl(M_YDST, sy * minfo->fbcon.var.xres_virtual >> 6); + mga_outl(M_LEN | M_EXEC, height); + WaitTillIdle(); + } + if (whattodo) { + u_int32_t step = minfo->fbcon.var.xres_virtual >> 1; + vaddr_t vbase = minfo->video.vbase; + if (whattodo & 1) { + unsigned int uaddr = sy * step + sx - 1; + u_int32_t loop; + u_int8_t bgx2 = bgx & 0xF0; + for (loop = height; loop > 0; loop --) { + mga_writeb(vbase, uaddr, (mga_readb(vbase, uaddr) & 0x0F) | bgx2); + uaddr += step; + } + } + if (whattodo & 2) { + unsigned int uaddr = sy * step + sx + width; + u_int32_t loop; + u_int8_t bgx2 = bgx & 0x0F; + for (loop = height; loop > 0; loop --) { + mga_writeb(vbase, uaddr, (mga_readb(vbase, uaddr) & 0xF0) | bgx2); + uaddr += step; + } + } + } + + CRITEND +} + +static void matroxfb_cfb4_fillrect(struct fb_info* info, const struct fb_fillrect* rect) { + struct matrox_fb_info *minfo = info2minfo(info); + + switch (rect->rop) { + case ROP_COPY: + matroxfb_cfb4_clear(minfo, ((u_int32_t *)info->pseudo_palette)[rect->color], rect->dy, rect->dx, rect->height, rect->width); + break; + } +} + +static void matroxfb_1bpp_imageblit(struct matrox_fb_info *minfo, u_int32_t fgx, + u_int32_t bgx, const u_int8_t *chardata, + int width, int height, int yy, int xx) +{ + u_int32_t step; + u_int32_t ydstlen; + u_int32_t xlen; + u_int32_t ar0; + u_int32_t charcell; + u_int32_t fxbndry; + vaddr_t mmio; + int easy; + CRITFLAGS + + DBG_HEAVY(__func__); + + step = (width + 7) >> 3; + charcell = height * step; + xlen = (charcell + 3) & ~3; + ydstlen = (yy << 16) | height; + if (width == step << 3) { + ar0 = height * width - 1; + easy = 1; + } else { + ar0 = width - 1; + easy = 0; + } + + CRITBEGIN + + mga_fifo(5); + matrox_accel_restore_maccess(minfo); + if (easy) + mga_outl(M_DWGCTL, M_DWG_ILOAD | M_DWG_SGNZERO | M_DWG_SHIFTZERO | M_DWG_BMONOWF | M_DWG_LINEAR | M_DWG_REPLACE); + else + mga_outl(M_DWGCTL, M_DWG_ILOAD | M_DWG_SGNZERO | M_DWG_SHIFTZERO | M_DWG_BMONOWF | M_DWG_REPLACE); + mga_outl(M_FCOL, fgx); + mga_outl(M_BCOL, bgx); + fxbndry = ((xx + width - 1) << 16) | xx; + mmio = minfo->mmio.vbase; + + mga_fifo(8); + matrox_accel_restore_maccess(minfo); + mga_writel(mmio, M_FXBNDRY, fxbndry); + mga_writel(mmio, M_AR0, ar0); + mga_writel(mmio, M_AR3, 0); + if (easy) { + mga_writel(mmio, M_YDSTLEN | M_EXEC, ydstlen); + mga_memcpy_toio(mmio, chardata, xlen); + } else { + mga_writel(mmio, M_AR5, 0); + mga_writel(mmio, M_YDSTLEN | M_EXEC, ydstlen); + if ((step & 3) == 0) { + /* Great. Source has 32bit aligned lines, so we can feed them + directly to the accelerator. */ + mga_memcpy_toio(mmio, chardata, charcell); + } else if (step == 1) { + /* Special case for 1..8bit widths */ + while (height--) { +#if defined(__BIG_ENDIAN) + fb_writel((*chardata) << 24, mmio.vaddr); +#else + fb_writel(*chardata, mmio.vaddr); +#endif + chardata++; + } + } else if (step == 2) { + /* Special case for 9..15bit widths */ + while (height--) { +#if defined(__BIG_ENDIAN) + fb_writel((*(u_int16_t*)chardata) << 16, mmio.vaddr); +#else + fb_writel(*(u_int16_t*)chardata, mmio.vaddr); +#endif + chardata += 2; + } + } else { + /* Tell... well, why bother... */ + while (height--) { + size_t i; + + for (i = 0; i < step; i += 4) { + /* Hope that there are at least three readable bytes beyond the end of bitmap */ + fb_writel(get_unaligned((u_int32_t*)(chardata + i)),mmio.vaddr); + } + chardata += step; + } + } + } + WaitTillIdle(); + CRITEND +} + + +static void matroxfb_imageblit(struct fb_info* info, const struct fb_image* image) { + struct matrox_fb_info *minfo = info2minfo(info); + + DBG_HEAVY(__func__); + + if (image->depth == 1) { + u_int32_t fgx, bgx; + + fgx = ((u_int32_t*)info->pseudo_palette)[image->fg_color]; + bgx = ((u_int32_t*)info->pseudo_palette)[image->bg_color]; + matroxfb_1bpp_imageblit(minfo, fgx, bgx, image->data, image->width, image->height, image->dy, image->dx); + } else { + /* Danger! image->depth is useless: logo painting code always + passes framebuffer color depth here, although logo data are + always 8bpp and info->pseudo_palette is changed to contain + logo palette to be used (but only for true/direct-color... sic...). + So do it completely in software... */ + cfb_imageblit(info, image); + } +} + +MODULE_DESCRIPTION("Accelerated fbops for Matrox Millennium/Mystique/G100/G200/G400/G450/G550"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_accel.h b/drivers/video/fbdev/matrox/matroxfb_accel.h new file mode 100644 index 000000000..a7aa9a1d2 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_accel.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MATROXFB_ACCEL_H__ +#define __MATROXFB_ACCEL_H__ + +#include "matroxfb_base.h" + +void matrox_cfbX_init(struct matrox_fb_info *minfo); + +#endif diff --git a/drivers/video/fbdev/matrox/matroxfb_base.c b/drivers/video/fbdev/matrox/matroxfb_base.c new file mode 100644 index 000000000..24d312c29 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_base.c @@ -0,0 +1,2598 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200 and G400 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * MTRR stuff: 1998 Tom Rini <trini@kernel.crashing.org> + * + * Contributors: "menion?" <menion@mindless.com> + * Betatesting, fixes, ideas + * + * "Kurt Garloff" <garloff@suse.de> + * Betatesting, fixes, ideas, videomodes, videomodes timmings + * + * "Tom Rini" <trini@kernel.crashing.org> + * MTRR stuff, PPC cleanups, betatesting, fixes, ideas + * + * "Bibek Sahu" <scorpio@dodds.net> + * Access device through readb|w|l and write b|w|l + * Extensive debugging stuff + * + * "Daniel Haun" <haund@usa.net> + * Testing, hardware cursor fixes + * + * "Scott Wood" <sawst46+@pitt.edu> + * Fixes + * + * "Gerd Knorr" <kraxel@goldbach.isdn.cs.tu-berlin.de> + * Betatesting + * + * "Kelly French" <targon@hazmat.com> + * "Fernando Herrera" <fherrera@eurielec.etsit.upm.es> + * Betatesting, bug reporting + * + * "Pablo Bianucci" <pbian@pccp.com.ar> + * Fixes, ideas, betatesting + * + * "Inaky Perez Gonzalez" <inaky@peloncho.fis.ucm.es> + * Fixes, enhandcements, ideas, betatesting + * + * "Ryuichi Oikawa" <roikawa@rr.iiij4u.or.jp> + * PPC betatesting, PPC support, backward compatibility + * + * "Paul Womar" <Paul@pwomar.demon.co.uk> + * "Owen Waller" <O.Waller@ee.qub.ac.uk> + * PPC betatesting + * + * "Thomas Pornin" <pornin@bolet.ens.fr> + * Alpha betatesting + * + * "Pieter van Leuven" <pvl@iae.nl> + * "Ulf Jaenicke-Roessler" <ujr@physik.phy.tu-dresden.de> + * G100 testing + * + * "H. Peter Arvin" <hpa@transmeta.com> + * Ideas + * + * "Cort Dougan" <cort@cs.nmt.edu> + * CHRP fixes and PReP cleanup + * + * "Mark Vojkovich" <mvojkovi@ucsd.edu> + * G400 support + * + * "Samuel Hocevar" <sam@via.ecp.fr> + * Fixes + * + * "Anton Altaparmakov" <AntonA@bigfoot.com> + * G400 MAX/non-MAX distinction + * + * "Ken Aaker" <kdaaker@rchland.vnet.ibm.com> + * memtype extension (needed for GXT130P RS/6000 adapter) + * + * "Uns Lider" <unslider@miranda.org> + * G100 PLNWT fixes + * + * "Denis Zaitsev" <zzz@cd-club.ru> + * Fixes + * + * "Mike Pieper" <mike@pieper-family.de> + * TVOut enhandcements, V4L2 control interface. + * + * "Diego Biurrun" <diego@biurrun.de> + * DFP testing + * + * (following author is not in any relation with this code, but his code + * is included in this driver) + * + * Based on framebuffer driver for VBE 2.0 compliant graphic boards + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + * (following author is not in any relation with this code, but his ideas + * were used when writing this driver) + * + * FreeVBE/AF (Matrox), "Shawn Hargreaves" <shawn@talula.demon.co.uk> + * + */ + +#include <linux/aperture.h> +#include <linux/export.h> +#include <linux/version.h> + +#include "matroxfb_base.h" +#include "matroxfb_misc.h" +#include "matroxfb_accel.h" +#include "matroxfb_DAC1064.h" +#include "matroxfb_Ti3026.h" +#include "matroxfb_maven.h" +#include "matroxfb_crtc2.h" +#include "matroxfb_g450.h" +#include <linux/matroxfb.h> +#include <linux/interrupt.h> +#include <linux/nvram.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#ifdef CONFIG_PPC_PMAC +#include <asm/machdep.h> +static int default_vmode = VMODE_NVRAM; +static int default_cmode = CMODE_NVRAM; +#endif + +static void matroxfb_unregister_device(struct matrox_fb_info* minfo); + +/* --------------------------------------------------------------------- */ + +/* + * card parameters + */ + +/* --------------------------------------------------------------------- */ + +static struct fb_var_screeninfo vesafb_defined = { + 640,480,640,480,/* W,H, W, H (virtual) load xres,xres_virtual*/ + 0,0, /* virtual -> visible no offset */ + 8, /* depth -> load bits_per_pixel */ + 0, /* greyscale ? */ + {0,0,0}, /* R */ + {0,0,0}, /* G */ + {0,0,0}, /* B */ + {0,0,0}, /* transparency */ + 0, /* standard pixel format */ + FB_ACTIVATE_NOW, + -1,-1, + FB_ACCELF_TEXT, /* accel flags */ + 39721L,48L,16L,33L,10L, + 96L,2L,~0, /* No sync info */ + FB_VMODE_NONINTERLACED, +}; + + + +/* --------------------------------------------------------------------- */ +static void update_crtc2(struct matrox_fb_info *minfo, unsigned int pos) +{ + struct matroxfb_dh_fb_info *info = minfo->crtc2.info; + + /* Make sure that displays are compatible */ + if (info && (info->fbcon.var.bits_per_pixel == minfo->fbcon.var.bits_per_pixel) + && (info->fbcon.var.xres_virtual == minfo->fbcon.var.xres_virtual) + && (info->fbcon.var.green.length == minfo->fbcon.var.green.length) + ) { + switch (minfo->fbcon.var.bits_per_pixel) { + case 16: + case 32: + pos = pos * 8; + if (info->interlaced) { + mga_outl(0x3C2C, pos); + mga_outl(0x3C28, pos + minfo->fbcon.var.xres_virtual * minfo->fbcon.var.bits_per_pixel / 8); + } else { + mga_outl(0x3C28, pos); + } + break; + } + } +} + +static void matroxfb_crtc1_panpos(struct matrox_fb_info *minfo) +{ + if (minfo->crtc1.panpos >= 0) { + unsigned long flags; + int panpos; + + matroxfb_DAC_lock_irqsave(flags); + panpos = minfo->crtc1.panpos; + if (panpos >= 0) { + unsigned int extvga_reg; + + minfo->crtc1.panpos = -1; /* No update pending anymore */ + extvga_reg = mga_inb(M_EXTVGA_INDEX); + mga_setr(M_EXTVGA_INDEX, 0x00, panpos); + if (extvga_reg != 0x00) { + mga_outb(M_EXTVGA_INDEX, extvga_reg); + } + } + matroxfb_DAC_unlock_irqrestore(flags); + } +} + +static irqreturn_t matrox_irq(int irq, void *dev_id) +{ + u_int32_t status; + int handled = 0; + struct matrox_fb_info *minfo = dev_id; + + status = mga_inl(M_STATUS); + + if (status & 0x20) { + mga_outl(M_ICLEAR, 0x20); + minfo->crtc1.vsync.cnt++; + matroxfb_crtc1_panpos(minfo); + wake_up_interruptible(&minfo->crtc1.vsync.wait); + handled = 1; + } + if (status & 0x200) { + mga_outl(M_ICLEAR, 0x200); + minfo->crtc2.vsync.cnt++; + wake_up_interruptible(&minfo->crtc2.vsync.wait); + handled = 1; + } + return IRQ_RETVAL(handled); +} + +int matroxfb_enable_irq(struct matrox_fb_info *minfo, int reenable) +{ + u_int32_t bm; + + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) + bm = 0x220; + else + bm = 0x020; + + if (!test_and_set_bit(0, &minfo->irq_flags)) { + if (request_irq(minfo->pcidev->irq, matrox_irq, + IRQF_SHARED, "matroxfb", minfo)) { + clear_bit(0, &minfo->irq_flags); + return -EINVAL; + } + /* Clear any pending field interrupts */ + mga_outl(M_ICLEAR, bm); + mga_outl(M_IEN, mga_inl(M_IEN) | bm); + } else if (reenable) { + u_int32_t ien; + + ien = mga_inl(M_IEN); + if ((ien & bm) != bm) { + printk(KERN_DEBUG "matroxfb: someone disabled IRQ [%08X]\n", ien); + mga_outl(M_IEN, ien | bm); + } + } + return 0; +} + +static void matroxfb_disable_irq(struct matrox_fb_info *minfo) +{ + if (test_and_clear_bit(0, &minfo->irq_flags)) { + /* Flush pending pan-at-vbl request... */ + matroxfb_crtc1_panpos(minfo); + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) + mga_outl(M_IEN, mga_inl(M_IEN) & ~0x220); + else + mga_outl(M_IEN, mga_inl(M_IEN) & ~0x20); + free_irq(minfo->pcidev->irq, minfo); + } +} + +int matroxfb_wait_for_sync(struct matrox_fb_info *minfo, u_int32_t crtc) +{ + struct matrox_vsync *vs; + unsigned int cnt; + int ret; + + switch (crtc) { + case 0: + vs = &minfo->crtc1.vsync; + break; + case 1: + if (minfo->devflags.accelerator != FB_ACCEL_MATROX_MGAG400) { + return -ENODEV; + } + vs = &minfo->crtc2.vsync; + break; + default: + return -ENODEV; + } + ret = matroxfb_enable_irq(minfo, 0); + if (ret) { + return ret; + } + + cnt = vs->cnt; + ret = wait_event_interruptible_timeout(vs->wait, cnt != vs->cnt, HZ/10); + if (ret < 0) { + return ret; + } + if (ret == 0) { + matroxfb_enable_irq(minfo, 1); + return -ETIMEDOUT; + } + return 0; +} + +/* --------------------------------------------------------------------- */ + +static void matrox_pan_var(struct matrox_fb_info *minfo, + struct fb_var_screeninfo *var) +{ + unsigned int pos; + unsigned short p0, p1, p2; + unsigned int p3; + int vbl; + unsigned long flags; + + CRITFLAGS + + DBG(__func__) + + if (minfo->dead) + return; + + minfo->fbcon.var.xoffset = var->xoffset; + minfo->fbcon.var.yoffset = var->yoffset; + pos = (minfo->fbcon.var.yoffset * minfo->fbcon.var.xres_virtual + minfo->fbcon.var.xoffset) * minfo->curr.final_bppShift / 32; + pos += minfo->curr.ydstorg.chunks; + p0 = minfo->hw.CRTC[0x0D] = pos & 0xFF; + p1 = minfo->hw.CRTC[0x0C] = (pos & 0xFF00) >> 8; + p2 = minfo->hw.CRTCEXT[0] = (minfo->hw.CRTCEXT[0] & 0xB0) | ((pos >> 16) & 0x0F) | ((pos >> 14) & 0x40); + p3 = minfo->hw.CRTCEXT[8] = pos >> 21; + + /* FB_ACTIVATE_VBL and we can acquire interrupts? Honor FB_ACTIVATE_VBL then... */ + vbl = (var->activate & FB_ACTIVATE_VBL) && (matroxfb_enable_irq(minfo, 0) == 0); + + CRITBEGIN + + matroxfb_DAC_lock_irqsave(flags); + mga_setr(M_CRTC_INDEX, 0x0D, p0); + mga_setr(M_CRTC_INDEX, 0x0C, p1); + if (minfo->devflags.support32MB) + mga_setr(M_EXTVGA_INDEX, 0x08, p3); + if (vbl) { + minfo->crtc1.panpos = p2; + } else { + /* Abort any pending change */ + minfo->crtc1.panpos = -1; + mga_setr(M_EXTVGA_INDEX, 0x00, p2); + } + matroxfb_DAC_unlock_irqrestore(flags); + + update_crtc2(minfo, pos); + + CRITEND +} + +static void matroxfb_remove(struct matrox_fb_info *minfo, int dummy) +{ + /* Currently we are holding big kernel lock on all dead & usecount updates. + * Destroy everything after all users release it. Especially do not unregister + * framebuffer and iounmap memory, neither fbmem nor fbcon-cfb* does not check + * for device unplugged when in use. + * In future we should point mmio.vbase & video.vbase somewhere where we can + * write data without causing too much damage... + */ + + minfo->dead = 1; + if (minfo->usecount) { + /* destroy it later */ + return; + } + matroxfb_unregister_device(minfo); + unregister_framebuffer(&minfo->fbcon); + matroxfb_g450_shutdown(minfo); + arch_phys_wc_del(minfo->wc_cookie); + kfree(minfo); +} + + /* + * Open/Release the frame buffer device + */ + +static int matroxfb_open(struct fb_info *info, int user) +{ + struct matrox_fb_info *minfo = info2minfo(info); + + DBG_LOOP(__func__) + + if (minfo->dead) { + return -ENXIO; + } + minfo->usecount++; + if (user) { + minfo->userusecount++; + } + return(0); +} + +static int matroxfb_release(struct fb_info *info, int user) +{ + struct matrox_fb_info *minfo = info2minfo(info); + + DBG_LOOP(__func__) + + if (user) { + if (0 == --minfo->userusecount) { + matroxfb_disable_irq(minfo); + } + } + if (!(--minfo->usecount) && minfo->dead) { + matroxfb_remove(minfo, 0); + } + return(0); +} + +static int matroxfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info* info) { + struct matrox_fb_info *minfo = info2minfo(info); + + DBG(__func__) + + matrox_pan_var(minfo, var); + return 0; +} + +static int matroxfb_get_final_bppShift(const struct matrox_fb_info *minfo, + int bpp) +{ + int bppshft2; + + DBG(__func__) + + bppshft2 = bpp; + if (!bppshft2) { + return 8; + } + if (isInterleave(minfo)) + bppshft2 >>= 1; + if (minfo->devflags.video64bits) + bppshft2 >>= 1; + return bppshft2; +} + +static int matroxfb_test_and_set_rounding(const struct matrox_fb_info *minfo, + int xres, int bpp) +{ + int over; + int rounding; + + DBG(__func__) + + switch (bpp) { + case 0: return xres; + case 4: rounding = 128; + break; + case 8: rounding = 64; /* doc says 64; 32 is OK for G400 */ + break; + case 16: rounding = 32; + break; + case 24: rounding = 64; /* doc says 64; 32 is OK for G400 */ + break; + default: rounding = 16; + /* on G400, 16 really does not work */ + if (minfo->devflags.accelerator == FB_ACCEL_MATROX_MGAG400) + rounding = 32; + break; + } + if (isInterleave(minfo)) { + rounding *= 2; + } + over = xres % rounding; + if (over) + xres += rounding-over; + return xres; +} + +static int matroxfb_pitch_adjust(const struct matrox_fb_info *minfo, int xres, + int bpp) +{ + const int* width; + int xres_new; + + DBG(__func__) + + if (!bpp) return xres; + + width = minfo->capable.vxres; + + if (minfo->devflags.precise_width) { + while (*width) { + if ((*width >= xres) && (matroxfb_test_and_set_rounding(minfo, *width, bpp) == *width)) { + break; + } + width++; + } + xres_new = *width; + } else { + xres_new = matroxfb_test_and_set_rounding(minfo, xres, bpp); + } + return xres_new; +} + +static int matroxfb_get_cmap_len(struct fb_var_screeninfo *var) { + + DBG(__func__) + + switch (var->bits_per_pixel) { + case 4: + return 16; /* pseudocolor... 16 entries HW palette */ + case 8: + return 256; /* pseudocolor... 256 entries HW palette */ + case 16: + return 16; /* directcolor... 16 entries SW palette */ + /* Mystique: truecolor, 16 entries SW palette, HW palette hardwired into 1:1 mapping */ + case 24: + return 16; /* directcolor... 16 entries SW palette */ + /* Mystique: truecolor, 16 entries SW palette, HW palette hardwired into 1:1 mapping */ + case 32: + return 16; /* directcolor... 16 entries SW palette */ + /* Mystique: truecolor, 16 entries SW palette, HW palette hardwired into 1:1 mapping */ + } + return 16; /* return something reasonable... or panic()? */ +} + +static int matroxfb_decode_var(const struct matrox_fb_info *minfo, + struct fb_var_screeninfo *var, int *visual, + int *video_cmap_len, unsigned int* ydstorg) +{ + struct RGBT { + unsigned char bpp; + struct { + unsigned char offset, + length; + } red, + green, + blue, + transp; + signed char visual; + }; + static const struct RGBT table[]= { + { 8,{ 0,8},{0,8},{0,8},{ 0,0},MX_VISUAL_PSEUDOCOLOR}, + {15,{10,5},{5,5},{0,5},{15,1},MX_VISUAL_DIRECTCOLOR}, + {16,{11,5},{5,6},{0,5},{ 0,0},MX_VISUAL_DIRECTCOLOR}, + {24,{16,8},{8,8},{0,8},{ 0,0},MX_VISUAL_DIRECTCOLOR}, + {32,{16,8},{8,8},{0,8},{24,8},MX_VISUAL_DIRECTCOLOR} + }; + struct RGBT const *rgbt; + unsigned int bpp = var->bits_per_pixel; + unsigned int vramlen; + unsigned int memlen; + + DBG(__func__) + + switch (bpp) { + case 4: if (!minfo->capable.cfb4) return -EINVAL; + break; + case 8: break; + case 16: break; + case 24: break; + case 32: break; + default: return -EINVAL; + } + *ydstorg = 0; + vramlen = minfo->video.len_usable; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + + var->xres_virtual = matroxfb_pitch_adjust(minfo, var->xres_virtual, bpp); + memlen = var->xres_virtual * bpp * var->yres_virtual / 8; + if (memlen > vramlen) { + var->yres_virtual = vramlen * 8 / (var->xres_virtual * bpp); + memlen = var->xres_virtual * bpp * var->yres_virtual / 8; + } + /* There is hardware bug that no line can cross 4MB boundary */ + /* give up for CFB24, it is impossible to easy workaround it */ + /* for other try to do something */ + if (!minfo->capable.cross4MB && (memlen > 0x400000)) { + if (bpp == 24) { + /* sorry */ + } else { + unsigned int linelen; + unsigned int m1 = linelen = var->xres_virtual * bpp / 8; + unsigned int m2 = PAGE_SIZE; /* or 128 if you do not need PAGE ALIGNED address */ + unsigned int max_yres; + + while (m1) { + while (m2 >= m1) m2 -= m1; + swap(m1, m2); + } + m2 = linelen * PAGE_SIZE / m2; + *ydstorg = m2 = 0x400000 % m2; + max_yres = (vramlen - m2) / linelen; + if (var->yres_virtual > max_yres) + var->yres_virtual = max_yres; + } + } + /* YDSTLEN contains only signed 16bit value */ + if (var->yres_virtual > 32767) + var->yres_virtual = 32767; + /* we must round yres/xres down, we already rounded y/xres_virtual up + if it was possible. We should return -EINVAL, but I disagree */ + if (var->yres_virtual < var->yres) + var->yres = var->yres_virtual; + if (var->xres_virtual < var->xres) + var->xres = var->xres_virtual; + if (var->xoffset + var->xres > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yoffset + var->yres > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + if (bpp == 16 && var->green.length == 5) { + bpp--; /* an artificial value - 15 */ + } + + for (rgbt = table; rgbt->bpp < bpp; rgbt++); +#define SETCLR(clr)\ + var->clr.offset = rgbt->clr.offset;\ + var->clr.length = rgbt->clr.length + SETCLR(red); + SETCLR(green); + SETCLR(blue); + SETCLR(transp); +#undef SETCLR + *visual = rgbt->visual; + + if (bpp > 8) + dprintk("matroxfb: truecolor: " + "size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n", + var->transp.length, var->red.length, var->green.length, var->blue.length, + var->transp.offset, var->red.offset, var->green.offset, var->blue.offset); + + *video_cmap_len = matroxfb_get_cmap_len(var); + dprintk(KERN_INFO "requested %d*%d/%dbpp (%d*%d)\n", var->xres, var->yres, var->bits_per_pixel, + var->xres_virtual, var->yres_virtual); + return 0; +} + +static int matroxfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *fb_info) +{ + struct matrox_fb_info* minfo = container_of(fb_info, struct matrox_fb_info, fbcon); + + DBG(__func__) + + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return + * != 0 for invalid regno. + */ + + if (regno >= minfo->curr.cmap_len) + return 1; + + if (minfo->fbcon.var.grayscale) { + /* gray = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + red = CNVT_TOHW(red, minfo->fbcon.var.red.length); + green = CNVT_TOHW(green, minfo->fbcon.var.green.length); + blue = CNVT_TOHW(blue, minfo->fbcon.var.blue.length); + transp = CNVT_TOHW(transp, minfo->fbcon.var.transp.length); + + switch (minfo->fbcon.var.bits_per_pixel) { + case 4: + case 8: + mga_outb(M_DAC_REG, regno); + mga_outb(M_DAC_VAL, red); + mga_outb(M_DAC_VAL, green); + mga_outb(M_DAC_VAL, blue); + break; + case 16: + if (regno >= 16) + break; + { + u_int16_t col = + (red << minfo->fbcon.var.red.offset) | + (green << minfo->fbcon.var.green.offset) | + (blue << minfo->fbcon.var.blue.offset) | + (transp << minfo->fbcon.var.transp.offset); /* for 1:5:5:5 */ + minfo->cmap[regno] = col | (col << 16); + } + break; + case 24: + case 32: + if (regno >= 16) + break; + minfo->cmap[regno] = + (red << minfo->fbcon.var.red.offset) | + (green << minfo->fbcon.var.green.offset) | + (blue << minfo->fbcon.var.blue.offset) | + (transp << minfo->fbcon.var.transp.offset); /* 8:8:8:8 */ + break; + } + return 0; +} + +static void matroxfb_init_fix(struct matrox_fb_info *minfo) +{ + struct fb_fix_screeninfo *fix = &minfo->fbcon.fix; + DBG(__func__) + + strcpy(fix->id,"MATROX"); + + fix->xpanstep = 8; /* 8 for 8bpp, 4 for 16bpp, 2 for 32bpp */ + fix->ypanstep = 1; + fix->ywrapstep = 0; + fix->mmio_start = minfo->mmio.base; + fix->mmio_len = minfo->mmio.len; + fix->accel = minfo->devflags.accelerator; +} + +static void matroxfb_update_fix(struct matrox_fb_info *minfo) +{ + struct fb_fix_screeninfo *fix = &minfo->fbcon.fix; + DBG(__func__) + + mutex_lock(&minfo->fbcon.mm_lock); + fix->smem_start = minfo->video.base + minfo->curr.ydstorg.bytes; + fix->smem_len = minfo->video.len_usable - minfo->curr.ydstorg.bytes; + mutex_unlock(&minfo->fbcon.mm_lock); +} + +static int matroxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + int err; + int visual; + int cmap_len; + unsigned int ydstorg; + struct matrox_fb_info *minfo = info2minfo(info); + + if (minfo->dead) { + return -ENXIO; + } + if ((err = matroxfb_decode_var(minfo, var, &visual, &cmap_len, &ydstorg)) != 0) + return err; + return 0; +} + +static int matroxfb_set_par(struct fb_info *info) +{ + int err; + int visual; + int cmap_len; + unsigned int ydstorg; + struct fb_var_screeninfo *var; + struct matrox_fb_info *minfo = info2minfo(info); + + DBG(__func__) + + if (minfo->dead) { + return -ENXIO; + } + + var = &info->var; + if ((err = matroxfb_decode_var(minfo, var, &visual, &cmap_len, &ydstorg)) != 0) + return err; + minfo->fbcon.screen_base = vaddr_va(minfo->video.vbase) + ydstorg; + matroxfb_update_fix(minfo); + minfo->fbcon.fix.visual = visual; + minfo->fbcon.fix.type = FB_TYPE_PACKED_PIXELS; + minfo->fbcon.fix.type_aux = 0; + minfo->fbcon.fix.line_length = (var->xres_virtual * var->bits_per_pixel) >> 3; + { + unsigned int pos; + + minfo->curr.cmap_len = cmap_len; + ydstorg += minfo->devflags.ydstorg; + minfo->curr.ydstorg.bytes = ydstorg; + minfo->curr.ydstorg.chunks = ydstorg >> (isInterleave(minfo) ? 3 : 2); + if (var->bits_per_pixel == 4) + minfo->curr.ydstorg.pixels = ydstorg; + else + minfo->curr.ydstorg.pixels = (ydstorg * 8) / var->bits_per_pixel; + minfo->curr.final_bppShift = matroxfb_get_final_bppShift(minfo, var->bits_per_pixel); + { struct my_timming mt; + struct matrox_hw_state* hw; + int out; + + matroxfb_var2my(var, &mt); + mt.crtc = MATROXFB_SRC_CRTC1; + /* CRTC1 delays */ + switch (var->bits_per_pixel) { + case 0: mt.delay = 31 + 0; break; + case 16: mt.delay = 21 + 8; break; + case 24: mt.delay = 17 + 8; break; + case 32: mt.delay = 16 + 8; break; + default: mt.delay = 31 + 8; break; + } + + hw = &minfo->hw; + + down_read(&minfo->altout.lock); + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC1 && + minfo->outputs[out].output->compute) { + minfo->outputs[out].output->compute(minfo->outputs[out].data, &mt); + } + } + up_read(&minfo->altout.lock); + minfo->crtc1.pixclock = mt.pixclock; + minfo->crtc1.mnp = mt.mnp; + minfo->hw_switch->init(minfo, &mt); + pos = (var->yoffset * var->xres_virtual + var->xoffset) * minfo->curr.final_bppShift / 32; + pos += minfo->curr.ydstorg.chunks; + + hw->CRTC[0x0D] = pos & 0xFF; + hw->CRTC[0x0C] = (pos & 0xFF00) >> 8; + hw->CRTCEXT[0] = (hw->CRTCEXT[0] & 0xF0) | ((pos >> 16) & 0x0F) | ((pos >> 14) & 0x40); + hw->CRTCEXT[8] = pos >> 21; + minfo->hw_switch->restore(minfo); + update_crtc2(minfo, pos); + down_read(&minfo->altout.lock); + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC1 && + minfo->outputs[out].output->program) { + minfo->outputs[out].output->program(minfo->outputs[out].data); + } + } + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC1 && + minfo->outputs[out].output->start) { + minfo->outputs[out].output->start(minfo->outputs[out].data); + } + } + up_read(&minfo->altout.lock); + matrox_cfbX_init(minfo); + } + } + minfo->initialized = 1; + return 0; +} + +static int matroxfb_get_vblank(struct matrox_fb_info *minfo, + struct fb_vblank *vblank) +{ + unsigned int sts1; + + matroxfb_enable_irq(minfo, 0); + memset(vblank, 0, sizeof(*vblank)); + vblank->flags = FB_VBLANK_HAVE_VCOUNT | FB_VBLANK_HAVE_VSYNC | + FB_VBLANK_HAVE_VBLANK | FB_VBLANK_HAVE_HBLANK; + sts1 = mga_inb(M_INSTS1); + vblank->vcount = mga_inl(M_VCOUNT); + /* BTW, on my PIII/450 with G400, reading M_INSTS1 + byte makes this call about 12% slower (1.70 vs. 2.05 us + per ioctl()) */ + if (sts1 & 1) + vblank->flags |= FB_VBLANK_HBLANKING; + if (sts1 & 8) + vblank->flags |= FB_VBLANK_VSYNCING; + if (vblank->vcount >= minfo->fbcon.var.yres) + vblank->flags |= FB_VBLANK_VBLANKING; + if (test_bit(0, &minfo->irq_flags)) { + vblank->flags |= FB_VBLANK_HAVE_COUNT; + /* Only one writer, aligned int value... + it should work without lock and without atomic_t */ + vblank->count = minfo->crtc1.vsync.cnt; + } + return 0; +} + +static struct matrox_altout panellink_output = { + .name = "Panellink output", +}; + +static int matroxfb_ioctl(struct fb_info *info, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct matrox_fb_info *minfo = info2minfo(info); + + DBG(__func__) + + if (minfo->dead) { + return -ENXIO; + } + + switch (cmd) { + case FBIOGET_VBLANK: + { + struct fb_vblank vblank; + int err; + + err = matroxfb_get_vblank(minfo, &vblank); + if (err) + return err; + if (copy_to_user(argp, &vblank, sizeof(vblank))) + return -EFAULT; + return 0; + } + case FBIO_WAITFORVSYNC: + { + u_int32_t crt; + + if (get_user(crt, (u_int32_t __user *)arg)) + return -EFAULT; + + return matroxfb_wait_for_sync(minfo, crt); + } + case MATROXFB_SET_OUTPUT_MODE: + { + struct matroxioc_output_mode mom; + struct matrox_altout *oproc; + int val; + + if (copy_from_user(&mom, argp, sizeof(mom))) + return -EFAULT; + if (mom.output >= MATROXFB_MAX_OUTPUTS) + return -ENXIO; + down_read(&minfo->altout.lock); + oproc = minfo->outputs[mom.output].output; + if (!oproc) { + val = -ENXIO; + } else if (!oproc->verifymode) { + if (mom.mode == MATROXFB_OUTPUT_MODE_MONITOR) { + val = 0; + } else { + val = -EINVAL; + } + } else { + val = oproc->verifymode(minfo->outputs[mom.output].data, mom.mode); + } + if (!val) { + if (minfo->outputs[mom.output].mode != mom.mode) { + minfo->outputs[mom.output].mode = mom.mode; + val = 1; + } + } + up_read(&minfo->altout.lock); + if (val != 1) + return val; + switch (minfo->outputs[mom.output].src) { + case MATROXFB_SRC_CRTC1: + matroxfb_set_par(info); + break; + case MATROXFB_SRC_CRTC2: + { + struct matroxfb_dh_fb_info* crtc2; + + down_read(&minfo->crtc2.lock); + crtc2 = minfo->crtc2.info; + if (crtc2) + crtc2->fbcon.fbops->fb_set_par(&crtc2->fbcon); + up_read(&minfo->crtc2.lock); + } + break; + } + return 0; + } + case MATROXFB_GET_OUTPUT_MODE: + { + struct matroxioc_output_mode mom; + struct matrox_altout *oproc; + int val; + + if (copy_from_user(&mom, argp, sizeof(mom))) + return -EFAULT; + if (mom.output >= MATROXFB_MAX_OUTPUTS) + return -ENXIO; + down_read(&minfo->altout.lock); + oproc = minfo->outputs[mom.output].output; + if (!oproc) { + val = -ENXIO; + } else { + mom.mode = minfo->outputs[mom.output].mode; + val = 0; + } + up_read(&minfo->altout.lock); + if (val) + return val; + if (copy_to_user(argp, &mom, sizeof(mom))) + return -EFAULT; + return 0; + } + case MATROXFB_SET_OUTPUT_CONNECTION: + { + u_int32_t tmp; + int i; + int changes; + + if (copy_from_user(&tmp, argp, sizeof(tmp))) + return -EFAULT; + for (i = 0; i < 32; i++) { + if (tmp & (1 << i)) { + if (i >= MATROXFB_MAX_OUTPUTS) + return -ENXIO; + if (!minfo->outputs[i].output) + return -ENXIO; + switch (minfo->outputs[i].src) { + case MATROXFB_SRC_NONE: + case MATROXFB_SRC_CRTC1: + break; + default: + return -EBUSY; + } + } + } + if (minfo->devflags.panellink) { + if (tmp & MATROXFB_OUTPUT_CONN_DFP) { + if (tmp & MATROXFB_OUTPUT_CONN_SECONDARY) + return -EINVAL; + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (minfo->outputs[i].src == MATROXFB_SRC_CRTC2) { + return -EBUSY; + } + } + } + } + changes = 0; + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (tmp & (1 << i)) { + if (minfo->outputs[i].src != MATROXFB_SRC_CRTC1) { + changes = 1; + minfo->outputs[i].src = MATROXFB_SRC_CRTC1; + } + } else if (minfo->outputs[i].src == MATROXFB_SRC_CRTC1) { + changes = 1; + minfo->outputs[i].src = MATROXFB_SRC_NONE; + } + } + if (!changes) + return 0; + matroxfb_set_par(info); + return 0; + } + case MATROXFB_GET_OUTPUT_CONNECTION: + { + u_int32_t conn = 0; + int i; + + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (minfo->outputs[i].src == MATROXFB_SRC_CRTC1) { + conn |= 1 << i; + } + } + if (put_user(conn, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + case MATROXFB_GET_AVAILABLE_OUTPUTS: + { + u_int32_t conn = 0; + int i; + + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (minfo->outputs[i].output) { + switch (minfo->outputs[i].src) { + case MATROXFB_SRC_NONE: + case MATROXFB_SRC_CRTC1: + conn |= 1 << i; + break; + } + } + } + if (minfo->devflags.panellink) { + if (conn & MATROXFB_OUTPUT_CONN_DFP) + conn &= ~MATROXFB_OUTPUT_CONN_SECONDARY; + if (conn & MATROXFB_OUTPUT_CONN_SECONDARY) + conn &= ~MATROXFB_OUTPUT_CONN_DFP; + } + if (put_user(conn, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + case MATROXFB_GET_ALL_OUTPUTS: + { + u_int32_t conn = 0; + int i; + + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + if (minfo->outputs[i].output) { + conn |= 1 << i; + } + } + if (put_user(conn, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + case VIDIOC_QUERYCAP: + { + struct v4l2_capability r; + + memset(&r, 0, sizeof(r)); + strcpy(r.driver, "matroxfb"); + strcpy(r.card, "Matrox"); + sprintf(r.bus_info, "PCI:%s", pci_name(minfo->pcidev)); + r.version = KERNEL_VERSION(1,0,0); + r.capabilities = V4L2_CAP_VIDEO_OUTPUT; + if (copy_to_user(argp, &r, sizeof(r))) + return -EFAULT; + return 0; + + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl qctrl; + int err; + + if (copy_from_user(&qctrl, argp, sizeof(qctrl))) + return -EFAULT; + + down_read(&minfo->altout.lock); + if (!minfo->outputs[1].output) { + err = -ENXIO; + } else if (minfo->outputs[1].output->getqueryctrl) { + err = minfo->outputs[1].output->getqueryctrl(minfo->outputs[1].data, &qctrl); + } else { + err = -EINVAL; + } + up_read(&minfo->altout.lock); + if (err >= 0 && + copy_to_user(argp, &qctrl, sizeof(qctrl))) + return -EFAULT; + return err; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control ctrl; + int err; + + if (copy_from_user(&ctrl, argp, sizeof(ctrl))) + return -EFAULT; + + down_read(&minfo->altout.lock); + if (!minfo->outputs[1].output) { + err = -ENXIO; + } else if (minfo->outputs[1].output->getctrl) { + err = minfo->outputs[1].output->getctrl(minfo->outputs[1].data, &ctrl); + } else { + err = -EINVAL; + } + up_read(&minfo->altout.lock); + if (err >= 0 && + copy_to_user(argp, &ctrl, sizeof(ctrl))) + return -EFAULT; + return err; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control ctrl; + int err; + + if (copy_from_user(&ctrl, argp, sizeof(ctrl))) + return -EFAULT; + + down_read(&minfo->altout.lock); + if (!minfo->outputs[1].output) { + err = -ENXIO; + } else if (minfo->outputs[1].output->setctrl) { + err = minfo->outputs[1].output->setctrl(minfo->outputs[1].data, &ctrl); + } else { + err = -EINVAL; + } + up_read(&minfo->altout.lock); + return err; + } + } + return -ENOTTY; +} + +/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */ + +static int matroxfb_blank(int blank, struct fb_info *info) +{ + int seq; + int crtc; + CRITFLAGS + struct matrox_fb_info *minfo = info2minfo(info); + + DBG(__func__) + + if (minfo->dead) + return 1; + + switch (blank) { + case FB_BLANK_NORMAL: seq = 0x20; crtc = 0x00; break; /* works ??? */ + case FB_BLANK_VSYNC_SUSPEND: seq = 0x20; crtc = 0x10; break; + case FB_BLANK_HSYNC_SUSPEND: seq = 0x20; crtc = 0x20; break; + case FB_BLANK_POWERDOWN: seq = 0x20; crtc = 0x30; break; + default: seq = 0x00; crtc = 0x00; break; + } + + CRITBEGIN + + mga_outb(M_SEQ_INDEX, 1); + mga_outb(M_SEQ_DATA, (mga_inb(M_SEQ_DATA) & ~0x20) | seq); + mga_outb(M_EXTVGA_INDEX, 1); + mga_outb(M_EXTVGA_DATA, (mga_inb(M_EXTVGA_DATA) & ~0x30) | crtc); + + CRITEND + return 0; +} + +static const struct fb_ops matroxfb_ops = { + .owner = THIS_MODULE, + .fb_open = matroxfb_open, + .fb_release = matroxfb_release, + __FB_DEFAULT_IOMEM_OPS_RDWR, + .fb_check_var = matroxfb_check_var, + .fb_set_par = matroxfb_set_par, + .fb_setcolreg = matroxfb_setcolreg, + .fb_pan_display =matroxfb_pan_display, + .fb_blank = matroxfb_blank, + .fb_ioctl = matroxfb_ioctl, +/* .fb_fillrect = <set by matrox_cfbX_init>, */ +/* .fb_copyarea = <set by matrox_cfbX_init>, */ +/* .fb_imageblit = <set by matrox_cfbX_init>, */ +/* .fb_cursor = <set by matrox_cfbX_init>, */ + __FB_DEFAULT_IOMEM_OPS_MMAP, +}; + +#define RSDepth(X) (((X) >> 8) & 0x0F) +#define RS8bpp 0x1 +#define RS15bpp 0x2 +#define RS16bpp 0x3 +#define RS32bpp 0x4 +#define RS4bpp 0x5 +#define RS24bpp 0x6 +#define RSText 0x7 +#define RSText8 0x8 +/* 9-F */ +static struct { struct fb_bitfield red, green, blue, transp; int bits_per_pixel; } colors[] = { + { { 0, 8, 0}, { 0, 8, 0}, { 0, 8, 0}, { 0, 0, 0}, 8 }, + { { 10, 5, 0}, { 5, 5, 0}, { 0, 5, 0}, { 15, 1, 0}, 16 }, + { { 11, 5, 0}, { 5, 6, 0}, { 0, 5, 0}, { 0, 0, 0}, 16 }, + { { 16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, { 24, 8, 0}, 32 }, + { { 0, 8, 0}, { 0, 8, 0}, { 0, 8, 0}, { 0, 0, 0}, 4 }, + { { 16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, { 0, 0, 0}, 24 }, + { { 0, 6, 0}, { 0, 6, 0}, { 0, 6, 0}, { 0, 0, 0}, 0 }, /* textmode with (default) VGA8x16 */ + { { 0, 6, 0}, { 0, 6, 0}, { 0, 6, 0}, { 0, 0, 0}, 0 }, /* textmode hardwired to VGA8x8 */ +}; + +/* initialized by setup, see explanation at end of file (search for MODULE_PARM_DESC) */ +static unsigned int mem; /* "matroxfb:mem:xxxxxM" */ +static int option_precise_width = 1; /* cannot be changed, option_precise_width==0 must imply noaccel */ +static int inv24; /* "matroxfb:inv24" */ +static int cross4MB = -1; /* "matroxfb:cross4MB" */ +static int disabled; /* "matroxfb:disabled" */ +static int noaccel; /* "matroxfb:noaccel" */ +static int nopan; /* "matroxfb:nopan" */ +static int no_pci_retry; /* "matroxfb:nopciretry" */ +static int novga; /* "matroxfb:novga" */ +static int nobios; /* "matroxfb:nobios" */ +static int noinit = 1; /* "matroxfb:init" */ +static int inverse; /* "matroxfb:inverse" */ +static int sgram; /* "matroxfb:sgram" */ +static int mtrr = 1; /* "matroxfb:nomtrr" */ +static int grayscale; /* "matroxfb:grayscale" */ +static int dev = -1; /* "matroxfb:dev:xxxxx" */ +static unsigned int vesa = ~0; /* "matroxfb:vesa:xxxxx" */ +static int depth = -1; /* "matroxfb:depth:xxxxx" */ +static unsigned int xres; /* "matroxfb:xres:xxxxx" */ +static unsigned int yres; /* "matroxfb:yres:xxxxx" */ +static unsigned int upper = ~0; /* "matroxfb:upper:xxxxx" */ +static unsigned int lower = ~0; /* "matroxfb:lower:xxxxx" */ +static unsigned int vslen; /* "matroxfb:vslen:xxxxx" */ +static unsigned int left = ~0; /* "matroxfb:left:xxxxx" */ +static unsigned int right = ~0; /* "matroxfb:right:xxxxx" */ +static unsigned int hslen; /* "matroxfb:hslen:xxxxx" */ +static unsigned int pixclock; /* "matroxfb:pixclock:xxxxx" */ +static int sync = -1; /* "matroxfb:sync:xxxxx" */ +static unsigned int fv; /* "matroxfb:fv:xxxxx" */ +static unsigned int fh; /* "matroxfb:fh:xxxxxk" */ +static unsigned int maxclk; /* "matroxfb:maxclk:xxxxM" */ +static int dfp; /* "matroxfb:dfp */ +static int dfp_type = -1; /* "matroxfb:dfp:xxx */ +static int memtype = -1; /* "matroxfb:memtype:xxx" */ +static char outputs[8]; /* "matroxfb:outputs:xxx" */ + +#ifndef MODULE +static char videomode[64]; /* "matroxfb:mode:xxxxx" or "matroxfb:xxxxx" */ +#endif + +static int matroxfb_getmemory(struct matrox_fb_info *minfo, + unsigned int maxSize, unsigned int *realSize) +{ + vaddr_t vm; + unsigned int offs; + unsigned int offs2; + unsigned char orig; + unsigned char bytes[32]; + unsigned char* tmp; + + DBG(__func__) + + vm = minfo->video.vbase; + maxSize &= ~0x1FFFFF; /* must be X*2MB (really it must be 2 or X*4MB) */ + /* at least 2MB */ + if (maxSize < 0x0200000) return 0; + if (maxSize > 0x2000000) maxSize = 0x2000000; + + mga_outb(M_EXTVGA_INDEX, 0x03); + orig = mga_inb(M_EXTVGA_DATA); + mga_outb(M_EXTVGA_DATA, orig | 0x80); + + tmp = bytes; + for (offs = 0x100000; offs < maxSize; offs += 0x200000) + *tmp++ = mga_readb(vm, offs); + for (offs = 0x100000; offs < maxSize; offs += 0x200000) + mga_writeb(vm, offs, 0x02); + mga_outb(M_CACHEFLUSH, 0x00); + for (offs = 0x100000; offs < maxSize; offs += 0x200000) { + if (mga_readb(vm, offs) != 0x02) + break; + mga_writeb(vm, offs, mga_readb(vm, offs) - 0x02); + if (mga_readb(vm, offs)) + break; + } + tmp = bytes; + for (offs2 = 0x100000; offs2 < maxSize; offs2 += 0x200000) + mga_writeb(vm, offs2, *tmp++); + + mga_outb(M_EXTVGA_INDEX, 0x03); + mga_outb(M_EXTVGA_DATA, orig); + + *realSize = offs - 0x100000; +#ifdef CONFIG_FB_MATROX_MILLENIUM + minfo->interleave = !(!isMillenium(minfo) || ((offs - 0x100000) & 0x3FFFFF)); +#endif + return 1; +} + +struct video_board { + int maxvram; + int maxdisplayable; + int accelID; + struct matrox_switch* lowlevel; + }; +#ifdef CONFIG_FB_MATROX_MILLENIUM +static struct video_board vbMillennium = { + .maxvram = 0x0800000, + .maxdisplayable = 0x0800000, + .accelID = FB_ACCEL_MATROX_MGA2064W, + .lowlevel = &matrox_millennium +}; + +static struct video_board vbMillennium2 = { + .maxvram = 0x1000000, + .maxdisplayable = 0x0800000, + .accelID = FB_ACCEL_MATROX_MGA2164W, + .lowlevel = &matrox_millennium +}; + +static struct video_board vbMillennium2A = { + .maxvram = 0x1000000, + .maxdisplayable = 0x0800000, + .accelID = FB_ACCEL_MATROX_MGA2164W_AGP, + .lowlevel = &matrox_millennium +}; +#endif /* CONFIG_FB_MATROX_MILLENIUM */ +#ifdef CONFIG_FB_MATROX_MYSTIQUE +static struct video_board vbMystique = { + .maxvram = 0x0800000, + .maxdisplayable = 0x0800000, + .accelID = FB_ACCEL_MATROX_MGA1064SG, + .lowlevel = &matrox_mystique +}; +#endif /* CONFIG_FB_MATROX_MYSTIQUE */ +#ifdef CONFIG_FB_MATROX_G +static struct video_board vbG100 = { + .maxvram = 0x0800000, + .maxdisplayable = 0x0800000, + .accelID = FB_ACCEL_MATROX_MGAG100, + .lowlevel = &matrox_G100 +}; + +static struct video_board vbG200 = { + .maxvram = 0x1000000, + .maxdisplayable = 0x1000000, + .accelID = FB_ACCEL_MATROX_MGAG200, + .lowlevel = &matrox_G100 +}; +static struct video_board vbG200eW = { + .maxvram = 0x1000000, + .maxdisplayable = 0x0800000, + .accelID = FB_ACCEL_MATROX_MGAG200, + .lowlevel = &matrox_G100 +}; +/* from doc it looks like that accelerator can draw only to low 16MB :-( Direct accesses & displaying are OK for + whole 32MB */ +static struct video_board vbG400 = { + .maxvram = 0x2000000, + .maxdisplayable = 0x1000000, + .accelID = FB_ACCEL_MATROX_MGAG400, + .lowlevel = &matrox_G100 +}; +#endif + +#define DEVF_VIDEO64BIT 0x0001 +#define DEVF_SWAPS 0x0002 +#define DEVF_SRCORG 0x0004 +#define DEVF_DUALHEAD 0x0008 +#define DEVF_CROSS4MB 0x0010 +#define DEVF_TEXT4B 0x0020 +/* #define DEVF_recycled 0x0040 */ +/* #define DEVF_recycled 0x0080 */ +#define DEVF_SUPPORT32MB 0x0100 +#define DEVF_ANY_VXRES 0x0200 +#define DEVF_TEXT16B 0x0400 +#define DEVF_CRTC2 0x0800 +#define DEVF_MAVEN_CAPABLE 0x1000 +#define DEVF_PANELLINK_CAPABLE 0x2000 +#define DEVF_G450DAC 0x4000 + +#define DEVF_GCORE (DEVF_VIDEO64BIT | DEVF_SWAPS | DEVF_CROSS4MB) +#define DEVF_G2CORE (DEVF_GCORE | DEVF_ANY_VXRES | DEVF_MAVEN_CAPABLE | DEVF_PANELLINK_CAPABLE | DEVF_SRCORG | DEVF_DUALHEAD) +#define DEVF_G100 (DEVF_GCORE) /* no doc, no vxres... */ +#define DEVF_G200 (DEVF_G2CORE) +#define DEVF_G400 (DEVF_G2CORE | DEVF_SUPPORT32MB | DEVF_TEXT16B | DEVF_CRTC2) +/* if you'll find how to drive DFP... */ +#define DEVF_G450 (DEVF_GCORE | DEVF_ANY_VXRES | DEVF_SUPPORT32MB | DEVF_TEXT16B | DEVF_CRTC2 | DEVF_G450DAC | DEVF_SRCORG | DEVF_DUALHEAD) +#define DEVF_G550 (DEVF_G450) + +static struct board { + unsigned short vendor, device, rev, svid, sid; + unsigned int flags; + unsigned int maxclk; + enum mga_chip chip; + struct video_board* base; + const char* name; + } dev_list[] = { +#ifdef CONFIG_FB_MATROX_MILLENIUM + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MIL, 0xFF, + 0, 0, + DEVF_TEXT4B, + 230000, + MGA_2064, + &vbMillennium, + "Millennium (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MIL_2, 0xFF, + 0, 0, + DEVF_SWAPS, + 220000, + MGA_2164, + &vbMillennium2, + "Millennium II (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MIL_2_AGP, 0xFF, + 0, 0, + DEVF_SWAPS, + 250000, + MGA_2164, + &vbMillennium2A, + "Millennium II (AGP)"}, +#endif +#ifdef CONFIG_FB_MATROX_MYSTIQUE + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MYS, 0x02, + 0, 0, + DEVF_VIDEO64BIT | DEVF_CROSS4MB, + 180000, + MGA_1064, + &vbMystique, + "Mystique (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MYS, 0xFF, + 0, 0, + DEVF_VIDEO64BIT | DEVF_SWAPS | DEVF_CROSS4MB, + 220000, + MGA_1164, + &vbMystique, + "Mystique 220 (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MYS_AGP, 0x02, + 0, 0, + DEVF_VIDEO64BIT | DEVF_CROSS4MB, + 180000, + MGA_1064, + &vbMystique, + "Mystique (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_MYS_AGP, 0xFF, + 0, 0, + DEVF_VIDEO64BIT | DEVF_SWAPS | DEVF_CROSS4MB, + 220000, + MGA_1164, + &vbMystique, + "Mystique 220 (AGP)"}, +#endif +#ifdef CONFIG_FB_MATROX_G + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G100_MM, 0xFF, + 0, 0, + DEVF_G100, + 230000, + MGA_G100, + &vbG100, + "MGA-G100 (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G100_AGP, 0xFF, + 0, 0, + DEVF_G100, + 230000, + MGA_G100, + &vbG100, + "MGA-G100 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_PCI, 0xFF, + 0, 0, + DEVF_G200, + 250000, + MGA_G200, + &vbG200, + "MGA-G200 (PCI)"}, + {PCI_VENDOR_ID_MATROX, 0x0532, 0xFF, + 0, 0, + DEVF_G200, + 250000, + MGA_G200, + &vbG200eW, + "MGA-G200eW (PCI)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_GENERIC, + DEVF_G200, + 220000, + MGA_G200, + &vbG200, + "MGA-G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_MYSTIQUE_G200_AGP, + DEVF_G200, + 230000, + MGA_G200, + &vbG200, + "Mystique G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_MILLENIUM_G200_AGP, + DEVF_G200, + 250000, + MGA_G200, + &vbG200, + "Millennium G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_MARVEL_G200_AGP, + DEVF_G200, + 230000, + MGA_G200, + &vbG200, + "Marvel G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + PCI_SS_VENDOR_ID_SIEMENS_NIXDORF, PCI_SS_ID_SIEMENS_MGA_G200_AGP, + DEVF_G200, + 230000, + MGA_G200, + &vbG200, + "MGA-G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G200_AGP, 0xFF, + 0, 0, + DEVF_G200, + 230000, + MGA_G200, + &vbG200, + "G200 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G400, 0x80, + PCI_SS_VENDOR_ID_MATROX, PCI_SS_ID_MATROX_MILLENNIUM_G400_MAX_AGP, + DEVF_G400, + 360000, + MGA_G400, + &vbG400, + "Millennium G400 MAX (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G400, 0x80, + 0, 0, + DEVF_G400, + 300000, + MGA_G400, + &vbG400, + "G400 (AGP)"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G400, 0xFF, + 0, 0, + DEVF_G450, + 360000, + MGA_G450, + &vbG400, + "G450"}, + {PCI_VENDOR_ID_MATROX, PCI_DEVICE_ID_MATROX_G550, 0xFF, + 0, 0, + DEVF_G550, + 360000, + MGA_G550, + &vbG400, + "G550"}, +#endif + {0, 0, 0xFF, + 0, 0, + 0, + 0, + 0, + NULL, + NULL}}; + +#ifndef MODULE +static const struct fb_videomode defaultmode = { + /* 640x480 @ 60Hz, 31.5 kHz */ + NULL, 60, 640, 480, 39721, 40, 24, 32, 11, 96, 2, + 0, FB_VMODE_NONINTERLACED +}; + +static int hotplug = 0; +#endif /* !MODULE */ + +static void setDefaultOutputs(struct matrox_fb_info *minfo) +{ + unsigned int i; + const char* ptr; + + minfo->outputs[0].default_src = MATROXFB_SRC_CRTC1; + if (minfo->devflags.g450dac) { + minfo->outputs[1].default_src = MATROXFB_SRC_CRTC1; + minfo->outputs[2].default_src = MATROXFB_SRC_CRTC1; + } else if (dfp) { + minfo->outputs[2].default_src = MATROXFB_SRC_CRTC1; + } + ptr = outputs; + for (i = 0; i < MATROXFB_MAX_OUTPUTS; i++) { + char c = *ptr++; + + if (c == 0) { + break; + } + if (c == '0') { + minfo->outputs[i].default_src = MATROXFB_SRC_NONE; + } else if (c == '1') { + minfo->outputs[i].default_src = MATROXFB_SRC_CRTC1; + } else if (c == '2' && minfo->devflags.crtc2) { + minfo->outputs[i].default_src = MATROXFB_SRC_CRTC2; + } else { + printk(KERN_ERR "matroxfb: Unknown outputs setting\n"); + break; + } + } + /* Nullify this option for subsequent adapters */ + outputs[0] = 0; +} + +static int initMatrox2(struct matrox_fb_info *minfo, struct board *b) +{ + unsigned long ctrlptr_phys = 0; + unsigned long video_base_phys = 0; + unsigned int memsize; + int err; + + static const struct pci_device_id intel_82437[] = { + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_82437) }, + { } + }; + + DBG(__func__) + + /* set default values... */ + vesafb_defined.accel_flags = FB_ACCELF_TEXT; + + minfo->hw_switch = b->base->lowlevel; + minfo->devflags.accelerator = b->base->accelID; + minfo->max_pixel_clock = b->maxclk; + + printk(KERN_INFO "matroxfb: Matrox %s detected\n", b->name); + minfo->capable.plnwt = 1; + minfo->chip = b->chip; + minfo->capable.srcorg = b->flags & DEVF_SRCORG; + minfo->devflags.video64bits = b->flags & DEVF_VIDEO64BIT; + if (b->flags & DEVF_TEXT4B) { + minfo->devflags.vgastep = 4; + minfo->devflags.textmode = 4; + minfo->devflags.text_type_aux = FB_AUX_TEXT_MGA_STEP16; + } else if (b->flags & DEVF_TEXT16B) { + minfo->devflags.vgastep = 16; + minfo->devflags.textmode = 1; + minfo->devflags.text_type_aux = FB_AUX_TEXT_MGA_STEP16; + } else { + minfo->devflags.vgastep = 8; + minfo->devflags.textmode = 1; + minfo->devflags.text_type_aux = FB_AUX_TEXT_MGA_STEP8; + } + minfo->devflags.support32MB = (b->flags & DEVF_SUPPORT32MB) != 0; + minfo->devflags.precise_width = !(b->flags & DEVF_ANY_VXRES); + minfo->devflags.crtc2 = (b->flags & DEVF_CRTC2) != 0; + minfo->devflags.maven_capable = (b->flags & DEVF_MAVEN_CAPABLE) != 0; + minfo->devflags.dualhead = (b->flags & DEVF_DUALHEAD) != 0; + minfo->devflags.dfp_type = dfp_type; + minfo->devflags.g450dac = (b->flags & DEVF_G450DAC) != 0; + minfo->devflags.textstep = minfo->devflags.vgastep * minfo->devflags.textmode; + minfo->devflags.textvram = 65536 / minfo->devflags.textmode; + setDefaultOutputs(minfo); + if (b->flags & DEVF_PANELLINK_CAPABLE) { + minfo->outputs[2].data = minfo; + minfo->outputs[2].output = &panellink_output; + minfo->outputs[2].src = minfo->outputs[2].default_src; + minfo->outputs[2].mode = MATROXFB_OUTPUT_MODE_MONITOR; + minfo->devflags.panellink = 1; + } + + if (minfo->capable.cross4MB < 0) + minfo->capable.cross4MB = b->flags & DEVF_CROSS4MB; + if (b->flags & DEVF_SWAPS) { + ctrlptr_phys = pci_resource_start(minfo->pcidev, 1); + video_base_phys = pci_resource_start(minfo->pcidev, 0); + minfo->devflags.fbResource = PCI_BASE_ADDRESS_0; + } else { + ctrlptr_phys = pci_resource_start(minfo->pcidev, 0); + video_base_phys = pci_resource_start(minfo->pcidev, 1); + minfo->devflags.fbResource = PCI_BASE_ADDRESS_1; + } + err = -EINVAL; + if (!ctrlptr_phys) { + printk(KERN_ERR "matroxfb: control registers are not available, matroxfb disabled\n"); + goto fail; + } + if (!video_base_phys) { + printk(KERN_ERR "matroxfb: video RAM is not available in PCI address space, matroxfb disabled\n"); + goto fail; + } + memsize = b->base->maxvram; + if (!devm_request_mem_region(&minfo->pcidev->dev, ctrlptr_phys, 16384, + "matroxfb MMIO")) { + goto fail; + } + if (!devm_request_mem_region(&minfo->pcidev->dev, video_base_phys, + memsize, "matroxfb FB")) { + goto fail; + } + minfo->video.len_maximum = memsize; + /* convert mem (autodetect k, M) */ + if (mem < 1024) mem *= 1024; + if (mem < 0x00100000) mem *= 1024; + + if (mem && (mem < memsize)) + memsize = mem; + err = -ENOMEM; + + minfo->mmio.vbase.vaddr = devm_ioremap(&minfo->pcidev->dev, ctrlptr_phys, 16384); + if (!minfo->mmio.vbase.vaddr) { + printk(KERN_ERR "matroxfb: cannot ioremap(%lX, 16384), matroxfb disabled\n", ctrlptr_phys); + goto fail; + } + minfo->mmio.base = ctrlptr_phys; + minfo->mmio.len = 16384; + minfo->video.base = video_base_phys; + minfo->video.vbase.vaddr = devm_ioremap_wc(&minfo->pcidev->dev, video_base_phys, memsize); + if (!minfo->video.vbase.vaddr) { + printk(KERN_ERR "matroxfb: cannot ioremap(%lX, %d), matroxfb disabled\n", + video_base_phys, memsize); + goto fail; + } + { + u_int32_t cmd; + u_int32_t mga_option; + + pci_read_config_dword(minfo->pcidev, PCI_OPTION_REG, &mga_option); + pci_read_config_dword(minfo->pcidev, PCI_COMMAND, &cmd); + mga_option &= 0x7FFFFFFF; /* clear BIG_ENDIAN */ + mga_option |= MX_OPTION_BSWAP; + /* disable palette snooping */ + cmd &= ~PCI_COMMAND_VGA_PALETTE; + if (pci_dev_present(intel_82437)) { + if (!(mga_option & 0x20000000) && !minfo->devflags.nopciretry) { + printk(KERN_WARNING "matroxfb: Disabling PCI retries due to i82437 present\n"); + } + mga_option |= 0x20000000; + minfo->devflags.nopciretry = 1; + } + pci_write_config_dword(minfo->pcidev, PCI_COMMAND, cmd); + pci_write_config_dword(minfo->pcidev, PCI_OPTION_REG, mga_option); + minfo->hw.MXoptionReg = mga_option; + + /* select non-DMA memory for PCI_MGA_DATA, otherwise dump of PCI cfg space can lock PCI bus */ + /* maybe preinit() candidate, but it is same... for all devices... at this time... */ + pci_write_config_dword(minfo->pcidev, PCI_MGA_INDEX, 0x00003C00); + } + + err = -ENXIO; + matroxfb_read_pins(minfo); + if (minfo->hw_switch->preinit(minfo)) { + goto failVideoIO; + } + + err = -ENOMEM; + if (!matroxfb_getmemory(minfo, memsize, &minfo->video.len) || !minfo->video.len) { + printk(KERN_ERR "matroxfb: cannot determine memory size\n"); + goto failVideoIO; + } + minfo->devflags.ydstorg = 0; + + minfo->video.base = video_base_phys; + minfo->video.len_usable = minfo->video.len; + if (minfo->video.len_usable > b->base->maxdisplayable) + minfo->video.len_usable = b->base->maxdisplayable; + if (mtrr) + minfo->wc_cookie = arch_phys_wc_add(video_base_phys, + minfo->video.len); + + if (!minfo->devflags.novga) + request_region(0x3C0, 32, "matrox"); + matroxfb_g450_connect(minfo); + minfo->hw_switch->reset(minfo); + + minfo->fbcon.monspecs.hfmin = 0; + minfo->fbcon.monspecs.hfmax = fh; + minfo->fbcon.monspecs.vfmin = 0; + minfo->fbcon.monspecs.vfmax = fv; + minfo->fbcon.monspecs.dpms = 0; /* TBD */ + + /* static settings */ + vesafb_defined.red = colors[depth-1].red; + vesafb_defined.green = colors[depth-1].green; + vesafb_defined.blue = colors[depth-1].blue; + vesafb_defined.bits_per_pixel = colors[depth-1].bits_per_pixel; + vesafb_defined.grayscale = grayscale; + vesafb_defined.vmode = 0; + if (noaccel) + vesafb_defined.accel_flags &= ~FB_ACCELF_TEXT; + + minfo->fbops = matroxfb_ops; + minfo->fbcon.fbops = &minfo->fbops; + minfo->fbcon.pseudo_palette = minfo->cmap; + minfo->fbcon.flags = FBINFO_PARTIAL_PAN_OK | /* Prefer panning for scroll under MC viewer/edit */ + FBINFO_HWACCEL_COPYAREA | /* We have hw-assisted bmove */ + FBINFO_HWACCEL_FILLRECT | /* And fillrect */ + FBINFO_HWACCEL_IMAGEBLIT | /* And imageblit */ + FBINFO_HWACCEL_XPAN | /* And we support both horizontal */ + FBINFO_HWACCEL_YPAN | /* And vertical panning */ + FBINFO_READS_FAST; + minfo->video.len_usable &= PAGE_MASK; + fb_alloc_cmap(&minfo->fbcon.cmap, 256, 1); + +#ifndef MODULE + /* mode database is marked __init!!! */ + if (!hotplug) { + fb_find_mode(&vesafb_defined, &minfo->fbcon, videomode[0] ? videomode : NULL, + NULL, 0, &defaultmode, vesafb_defined.bits_per_pixel); + } +#endif /* !MODULE */ + + /* mode modifiers */ + if (hslen) + vesafb_defined.hsync_len = hslen; + if (vslen) + vesafb_defined.vsync_len = vslen; + if (left != ~0) + vesafb_defined.left_margin = left; + if (right != ~0) + vesafb_defined.right_margin = right; + if (upper != ~0) + vesafb_defined.upper_margin = upper; + if (lower != ~0) + vesafb_defined.lower_margin = lower; + if (xres) + vesafb_defined.xres = xres; + if (yres) + vesafb_defined.yres = yres; + if (sync != -1) + vesafb_defined.sync = sync; + else if (vesafb_defined.sync == ~0) { + vesafb_defined.sync = 0; + if (yres < 400) + vesafb_defined.sync |= FB_SYNC_HOR_HIGH_ACT; + else if (yres < 480) + vesafb_defined.sync |= FB_SYNC_VERT_HIGH_ACT; + } + + /* fv, fh, maxclk limits was specified */ + { + unsigned int tmp; + + if (fv) { + tmp = fv * (vesafb_defined.upper_margin + vesafb_defined.yres + + vesafb_defined.lower_margin + vesafb_defined.vsync_len); + if ((tmp < fh) || (fh == 0)) fh = tmp; + } + if (fh) { + tmp = fh * (vesafb_defined.left_margin + vesafb_defined.xres + + vesafb_defined.right_margin + vesafb_defined.hsync_len); + if ((tmp < maxclk) || (maxclk == 0)) maxclk = tmp; + } + tmp = (maxclk + 499) / 500; + if (tmp) { + tmp = (2000000000 + tmp) / tmp; + if (tmp > pixclock) pixclock = tmp; + } + } + if (pixclock) { + if (pixclock < 2000) /* > 500MHz */ + pixclock = 4000; /* 250MHz */ + if (pixclock > 1000000) + pixclock = 1000000; /* 1MHz */ + vesafb_defined.pixclock = pixclock; + } + + /* FIXME: Where to move this?! */ +#if defined(CONFIG_PPC_PMAC) +#ifndef MODULE + if (machine_is(powermac)) { + struct fb_var_screeninfo var; + + if (default_vmode <= 0 || default_vmode > VMODE_MAX) + default_vmode = VMODE_640_480_60; +#if defined(CONFIG_PPC32) + if (IS_REACHABLE(CONFIG_NVRAM) && default_cmode == CMODE_NVRAM) + default_cmode = nvram_read_byte(NV_CMODE); +#endif + if (default_cmode < CMODE_8 || default_cmode > CMODE_32) + default_cmode = CMODE_8; + if (!mac_vmode_to_var(default_vmode, default_cmode, &var)) { + var.accel_flags = vesafb_defined.accel_flags; + var.xoffset = var.yoffset = 0; + /* Note: mac_vmode_to_var() does not set all parameters */ + vesafb_defined = var; + } + } +#endif /* !MODULE */ +#endif /* CONFIG_PPC_PMAC */ + vesafb_defined.xres_virtual = vesafb_defined.xres; + if (nopan) { + vesafb_defined.yres_virtual = vesafb_defined.yres; + } else { + vesafb_defined.yres_virtual = 65536; /* large enough to be INF, but small enough + to yres_virtual * xres_virtual < 2^32 */ + } + matroxfb_init_fix(minfo); + minfo->fbcon.screen_base = vaddr_va(minfo->video.vbase); + /* Normalize values (namely yres_virtual) */ + matroxfb_check_var(&vesafb_defined, &minfo->fbcon); + /* And put it into "current" var. Do NOT program hardware yet, or we'll not take over + * vgacon correctly. fbcon_startup will call fb_set_par for us, WITHOUT check_var, + * and unfortunately it will do it BEFORE vgacon contents is saved, so it won't work + * anyway. But we at least tried... */ + minfo->fbcon.var = vesafb_defined; + err = -EINVAL; + + printk(KERN_INFO "matroxfb: %dx%dx%dbpp (virtual: %dx%d)\n", + vesafb_defined.xres, vesafb_defined.yres, vesafb_defined.bits_per_pixel, + vesafb_defined.xres_virtual, vesafb_defined.yres_virtual); + printk(KERN_INFO "matroxfb: framebuffer at 0x%lX, mapped to 0x%p, size %d\n", + minfo->video.base, vaddr_va(minfo->video.vbase), minfo->video.len); + +/* We do not have to set currcon to 0... register_framebuffer do it for us on first console + * and we do not want currcon == 0 for subsequent framebuffers */ + + minfo->fbcon.device = &minfo->pcidev->dev; + if (register_framebuffer(&minfo->fbcon) < 0) { + goto failVideoIO; + } + fb_info(&minfo->fbcon, "%s frame buffer device\n", minfo->fbcon.fix.id); + + /* there is no console on this fb... but we have to initialize hardware + * until someone tells me what is proper thing to do */ + if (!minfo->initialized) { + fb_info(&minfo->fbcon, "initializing hardware\n"); + /* We have to use FB_ACTIVATE_FORCE, as we had to put vesafb_defined to the fbcon.var + * already before, so register_framebuffer works correctly. */ + vesafb_defined.activate |= FB_ACTIVATE_FORCE; + fb_set_var(&minfo->fbcon, &vesafb_defined); + } + + return 0; +failVideoIO:; + matroxfb_g450_shutdown(minfo); +fail:; + return err; +} + +static LIST_HEAD(matroxfb_list); +static LIST_HEAD(matroxfb_driver_list); + +#define matroxfb_l(x) list_entry(x, struct matrox_fb_info, next_fb) +#define matroxfb_driver_l(x) list_entry(x, struct matroxfb_driver, node) +int matroxfb_register_driver(struct matroxfb_driver* drv) { + struct matrox_fb_info* minfo; + + list_add(&drv->node, &matroxfb_driver_list); + list_for_each_entry(minfo, &matroxfb_list, next_fb) { + void* p; + + if (minfo->drivers_count == MATROXFB_MAX_FB_DRIVERS) + continue; + p = drv->probe(minfo); + if (p) { + minfo->drivers_data[minfo->drivers_count] = p; + minfo->drivers[minfo->drivers_count++] = drv; + } + } + return 0; +} + +void matroxfb_unregister_driver(struct matroxfb_driver* drv) { + struct matrox_fb_info* minfo; + + list_del(&drv->node); + list_for_each_entry(minfo, &matroxfb_list, next_fb) { + int i; + + for (i = 0; i < minfo->drivers_count; ) { + if (minfo->drivers[i] == drv) { + if (drv && drv->remove) + drv->remove(minfo, minfo->drivers_data[i]); + minfo->drivers[i] = minfo->drivers[--minfo->drivers_count]; + minfo->drivers_data[i] = minfo->drivers_data[minfo->drivers_count]; + } else + i++; + } + } +} + +static void matroxfb_register_device(struct matrox_fb_info* minfo) { + struct matroxfb_driver* drv; + int i = 0; + list_add(&minfo->next_fb, &matroxfb_list); + for (drv = matroxfb_driver_l(matroxfb_driver_list.next); + drv != matroxfb_driver_l(&matroxfb_driver_list); + drv = matroxfb_driver_l(drv->node.next)) { + if (drv->probe) { + void *p = drv->probe(minfo); + if (p) { + minfo->drivers_data[i] = p; + minfo->drivers[i++] = drv; + if (i == MATROXFB_MAX_FB_DRIVERS) + break; + } + } + } + minfo->drivers_count = i; +} + +static void matroxfb_unregister_device(struct matrox_fb_info* minfo) { + int i; + + list_del(&minfo->next_fb); + for (i = 0; i < minfo->drivers_count; i++) { + struct matroxfb_driver* drv = minfo->drivers[i]; + + if (drv && drv->remove) + drv->remove(minfo, minfo->drivers_data[i]); + } +} + +static int matroxfb_probe(struct pci_dev* pdev, const struct pci_device_id* dummy) { + struct board* b; + u_int16_t svid; + u_int16_t sid; + struct matrox_fb_info* minfo; + int err; + u_int32_t cmd; + DBG(__func__) + + err = aperture_remove_conflicting_pci_devices(pdev, "matroxfb"); + if (err) + return err; + + svid = pdev->subsystem_vendor; + sid = pdev->subsystem_device; + for (b = dev_list; b->vendor; b++) { + if ((b->vendor != pdev->vendor) || (b->device != pdev->device) || (b->rev < pdev->revision)) continue; + if (b->svid) + if ((b->svid != svid) || (b->sid != sid)) continue; + break; + } + /* not match... */ + if (!b->vendor) + return -ENODEV; + if (dev > 0) { + /* not requested one... */ + dev--; + return -ENODEV; + } + pci_read_config_dword(pdev, PCI_COMMAND, &cmd); + if (pcim_enable_device(pdev)) + return -1; + + minfo = kzalloc_obj(*minfo); + if (!minfo) + return -ENOMEM; + + minfo->pcidev = pdev; + minfo->dead = 0; + minfo->usecount = 0; + minfo->userusecount = 0; + + pci_set_drvdata(pdev, minfo); + /* DEVFLAGS */ + minfo->devflags.memtype = memtype; + if (memtype != -1) + noinit = 0; + if (cmd & PCI_COMMAND_MEMORY) { + minfo->devflags.novga = novga; + minfo->devflags.nobios = nobios; + minfo->devflags.noinit = noinit; + /* subsequent heads always needs initialization and must not enable BIOS */ + novga = 1; + nobios = 1; + noinit = 0; + } else { + minfo->devflags.novga = 1; + minfo->devflags.nobios = 1; + minfo->devflags.noinit = 0; + } + + minfo->devflags.nopciretry = no_pci_retry; + minfo->devflags.mga_24bpp_fix = inv24; + minfo->devflags.precise_width = option_precise_width; + minfo->devflags.sgram = sgram; + minfo->capable.cross4MB = cross4MB; + + spin_lock_init(&minfo->lock.DAC); + spin_lock_init(&minfo->lock.accel); + init_rwsem(&minfo->crtc2.lock); + init_rwsem(&minfo->altout.lock); + mutex_init(&minfo->fbcon.mm_lock); + minfo->irq_flags = 0; + init_waitqueue_head(&minfo->crtc1.vsync.wait); + init_waitqueue_head(&minfo->crtc2.vsync.wait); + minfo->crtc1.panpos = -1; + + err = initMatrox2(minfo, b); + if (!err) { + matroxfb_register_device(minfo); + return 0; + } + kfree(minfo); + return -1; +} + +static void pci_remove_matrox(struct pci_dev* pdev) { + struct matrox_fb_info* minfo; + + minfo = pci_get_drvdata(pdev); + matroxfb_remove(minfo, 1); +} + +static const struct pci_device_id matroxfb_devices[] = { +#ifdef CONFIG_FB_MATROX_MILLENIUM + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_MIL) }, + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_MIL_2) }, + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_MIL_2_AGP) }, +#endif +#ifdef CONFIG_FB_MATROX_MYSTIQUE + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_MYS) }, +#endif +#ifdef CONFIG_FB_MATROX_G + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_G100_MM) }, + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_G100_AGP) }, + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_G200_PCI) }, + { PCI_VDEVICE(MATROX, 0x0532) }, + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_G200_AGP) }, + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_G400) }, + { PCI_VDEVICE(MATROX, PCI_DEVICE_ID_MATROX_G550) }, +#endif + { } +}; + +MODULE_DEVICE_TABLE(pci, matroxfb_devices); + + +static struct pci_driver matroxfb_driver = { + .name = "matroxfb", + .id_table = matroxfb_devices, + .probe = matroxfb_probe, + .remove = pci_remove_matrox, +}; + +/* **************************** init-time only **************************** */ + +#define RSResolution(X) ((X) & 0x0F) +#define RS640x400 1 +#define RS640x480 2 +#define RS800x600 3 +#define RS1024x768 4 +#define RS1280x1024 5 +#define RS1600x1200 6 +#define RS768x576 7 +#define RS960x720 8 +#define RS1152x864 9 +#define RS1408x1056 10 +#define RS640x350 11 +#define RS1056x344 12 /* 132 x 43 text */ +#define RS1056x400 13 /* 132 x 50 text */ +#define RS1056x480 14 /* 132 x 60 text */ +#define RSNoxNo 15 +/* 10-FF */ +static struct { int xres, yres, left, right, upper, lower, hslen, vslen, vfreq; } timmings[] __initdata = { + { 640, 400, 48, 16, 39, 8, 96, 2, 70 }, + { 640, 480, 48, 16, 33, 10, 96, 2, 60 }, + { 800, 600, 144, 24, 28, 8, 112, 6, 60 }, + { 1024, 768, 160, 32, 30, 4, 128, 4, 60 }, + { 1280, 1024, 224, 32, 32, 4, 136, 4, 60 }, + { 1600, 1200, 272, 48, 32, 5, 152, 5, 60 }, + { 768, 576, 144, 16, 28, 6, 112, 4, 60 }, + { 960, 720, 144, 24, 28, 8, 112, 4, 60 }, + { 1152, 864, 192, 32, 30, 4, 128, 4, 60 }, + { 1408, 1056, 256, 40, 32, 5, 144, 5, 60 }, + { 640, 350, 48, 16, 39, 8, 96, 2, 70 }, + { 1056, 344, 96, 24, 59, 44, 160, 2, 70 }, + { 1056, 400, 96, 24, 39, 8, 160, 2, 70 }, + { 1056, 480, 96, 24, 36, 12, 160, 3, 60 }, + { 0, 0, ~0, ~0, ~0, ~0, 0, 0, 0 } +}; + +#define RSCreate(X,Y) ((X) | ((Y) << 8)) +static struct { unsigned int vesa; unsigned int info; } *RSptr, vesamap[] __initdata = { +/* default must be first */ + { ~0, RSCreate(RSNoxNo, RS8bpp ) }, + { 0x101, RSCreate(RS640x480, RS8bpp ) }, + { 0x100, RSCreate(RS640x400, RS8bpp ) }, + { 0x180, RSCreate(RS768x576, RS8bpp ) }, + { 0x103, RSCreate(RS800x600, RS8bpp ) }, + { 0x188, RSCreate(RS960x720, RS8bpp ) }, + { 0x105, RSCreate(RS1024x768, RS8bpp ) }, + { 0x190, RSCreate(RS1152x864, RS8bpp ) }, + { 0x107, RSCreate(RS1280x1024, RS8bpp ) }, + { 0x198, RSCreate(RS1408x1056, RS8bpp ) }, + { 0x11C, RSCreate(RS1600x1200, RS8bpp ) }, + { 0x110, RSCreate(RS640x480, RS15bpp) }, + { 0x181, RSCreate(RS768x576, RS15bpp) }, + { 0x113, RSCreate(RS800x600, RS15bpp) }, + { 0x189, RSCreate(RS960x720, RS15bpp) }, + { 0x116, RSCreate(RS1024x768, RS15bpp) }, + { 0x191, RSCreate(RS1152x864, RS15bpp) }, + { 0x119, RSCreate(RS1280x1024, RS15bpp) }, + { 0x199, RSCreate(RS1408x1056, RS15bpp) }, + { 0x11D, RSCreate(RS1600x1200, RS15bpp) }, + { 0x111, RSCreate(RS640x480, RS16bpp) }, + { 0x182, RSCreate(RS768x576, RS16bpp) }, + { 0x114, RSCreate(RS800x600, RS16bpp) }, + { 0x18A, RSCreate(RS960x720, RS16bpp) }, + { 0x117, RSCreate(RS1024x768, RS16bpp) }, + { 0x192, RSCreate(RS1152x864, RS16bpp) }, + { 0x11A, RSCreate(RS1280x1024, RS16bpp) }, + { 0x19A, RSCreate(RS1408x1056, RS16bpp) }, + { 0x11E, RSCreate(RS1600x1200, RS16bpp) }, + { 0x1B2, RSCreate(RS640x480, RS24bpp) }, + { 0x184, RSCreate(RS768x576, RS24bpp) }, + { 0x1B5, RSCreate(RS800x600, RS24bpp) }, + { 0x18C, RSCreate(RS960x720, RS24bpp) }, + { 0x1B8, RSCreate(RS1024x768, RS24bpp) }, + { 0x194, RSCreate(RS1152x864, RS24bpp) }, + { 0x1BB, RSCreate(RS1280x1024, RS24bpp) }, + { 0x19C, RSCreate(RS1408x1056, RS24bpp) }, + { 0x1BF, RSCreate(RS1600x1200, RS24bpp) }, + { 0x112, RSCreate(RS640x480, RS32bpp) }, + { 0x183, RSCreate(RS768x576, RS32bpp) }, + { 0x115, RSCreate(RS800x600, RS32bpp) }, + { 0x18B, RSCreate(RS960x720, RS32bpp) }, + { 0x118, RSCreate(RS1024x768, RS32bpp) }, + { 0x193, RSCreate(RS1152x864, RS32bpp) }, + { 0x11B, RSCreate(RS1280x1024, RS32bpp) }, + { 0x19B, RSCreate(RS1408x1056, RS32bpp) }, + { 0x11F, RSCreate(RS1600x1200, RS32bpp) }, + { 0x010, RSCreate(RS640x350, RS4bpp ) }, + { 0x012, RSCreate(RS640x480, RS4bpp ) }, + { 0x102, RSCreate(RS800x600, RS4bpp ) }, + { 0x104, RSCreate(RS1024x768, RS4bpp ) }, + { 0x106, RSCreate(RS1280x1024, RS4bpp ) }, + { 0, 0 }}; + +static void __init matroxfb_init_params(void) { + /* fh from kHz to Hz */ + if (fh < 1000) + fh *= 1000; /* 1kHz minimum */ + /* maxclk */ + if (maxclk < 1000) maxclk *= 1000; /* kHz -> Hz, MHz -> kHz */ + if (maxclk < 1000000) maxclk *= 1000; /* kHz -> Hz, 1MHz minimum */ + /* fix VESA number */ + if (vesa != ~0) + vesa &= 0x1DFF; /* mask out clearscreen, acceleration and so on */ + + /* static settings */ + for (RSptr = vesamap; RSptr->vesa; RSptr++) { + if (RSptr->vesa == vesa) break; + } + if (!RSptr->vesa) { + printk(KERN_ERR "Invalid vesa mode 0x%04X\n", vesa); + RSptr = vesamap; + } + { + int res = RSResolution(RSptr->info)-1; + if (left == ~0) + left = timmings[res].left; + if (!xres) + xres = timmings[res].xres; + if (right == ~0) + right = timmings[res].right; + if (!hslen) + hslen = timmings[res].hslen; + if (upper == ~0) + upper = timmings[res].upper; + if (!yres) + yres = timmings[res].yres; + if (lower == ~0) + lower = timmings[res].lower; + if (!vslen) + vslen = timmings[res].vslen; + if (!(fv||fh||maxclk||pixclock)) + fv = timmings[res].vfreq; + if (depth == -1) + depth = RSDepth(RSptr->info); + } +} + +static int __init matrox_init(void) { + int err; + + if (fb_modesetting_disabled("matroxfb")) + return -ENODEV; + + matroxfb_init_params(); + err = pci_register_driver(&matroxfb_driver); + dev = -1; /* accept all new devices... */ + return err; +} + +/* **************************** exit-time only **************************** */ + +static void __exit matrox_done(void) { + pci_unregister_driver(&matroxfb_driver); +} + +#ifndef MODULE + +/* ************************* init in-kernel code ************************** */ + +static int __init matroxfb_setup(char *options) { + char *this_opt; + + DBG(__func__) + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) continue; + + dprintk("matroxfb_setup: option %s\n", this_opt); + + if (!strncmp(this_opt, "dev:", 4)) + dev = simple_strtoul(this_opt+4, NULL, 0); + else if (!strncmp(this_opt, "depth:", 6)) { + switch (simple_strtoul(this_opt+6, NULL, 0)) { + case 0: depth = RSText; break; + case 4: depth = RS4bpp; break; + case 8: depth = RS8bpp; break; + case 15:depth = RS15bpp; break; + case 16:depth = RS16bpp; break; + case 24:depth = RS24bpp; break; + case 32:depth = RS32bpp; break; + default: + printk(KERN_ERR "matroxfb: unsupported color depth\n"); + } + } else if (!strncmp(this_opt, "xres:", 5)) + xres = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "yres:", 5)) + yres = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "vslen:", 6)) + vslen = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "hslen:", 6)) + hslen = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "left:", 5)) + left = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "right:", 6)) + right = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "upper:", 6)) + upper = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "lower:", 6)) + lower = simple_strtoul(this_opt+6, NULL, 0); + else if (!strncmp(this_opt, "pixclock:", 9)) + pixclock = simple_strtoul(this_opt+9, NULL, 0); + else if (!strncmp(this_opt, "sync:", 5)) + sync = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "vesa:", 5)) + vesa = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "maxclk:", 7)) + maxclk = simple_strtoul(this_opt+7, NULL, 0); + else if (!strncmp(this_opt, "fh:", 3)) + fh = simple_strtoul(this_opt+3, NULL, 0); + else if (!strncmp(this_opt, "fv:", 3)) + fv = simple_strtoul(this_opt+3, NULL, 0); + else if (!strncmp(this_opt, "mem:", 4)) + mem = simple_strtoul(this_opt+4, NULL, 0); + else if (!strncmp(this_opt, "mode:", 5)) + strscpy(videomode, this_opt + 5, sizeof(videomode)); + else if (!strncmp(this_opt, "outputs:", 8)) + strscpy(outputs, this_opt + 8, sizeof(outputs)); + else if (!strncmp(this_opt, "dfp:", 4)) { + dfp_type = simple_strtoul(this_opt+4, NULL, 0); + dfp = 1; + } +#ifdef CONFIG_PPC_PMAC + else if (!strncmp(this_opt, "vmode:", 6)) { + unsigned int vmode = simple_strtoul(this_opt+6, NULL, 0); + if (vmode > 0 && vmode <= VMODE_MAX) + default_vmode = vmode; + } else if (!strncmp(this_opt, "cmode:", 6)) { + unsigned int cmode = simple_strtoul(this_opt+6, NULL, 0); + switch (cmode) { + case 0: + case 8: + default_cmode = CMODE_8; + break; + case 15: + case 16: + default_cmode = CMODE_16; + break; + case 24: + case 32: + default_cmode = CMODE_32; + break; + } + } +#endif + else if (!strcmp(this_opt, "disabled")) /* nodisabled does not exist */ + disabled = 1; + else if (!strcmp(this_opt, "enabled")) /* noenabled does not exist */ + disabled = 0; + else if (!strcmp(this_opt, "sgram")) /* nosgram == sdram */ + sgram = 1; + else if (!strcmp(this_opt, "sdram")) + sgram = 0; + else if (!strncmp(this_opt, "memtype:", 8)) + memtype = simple_strtoul(this_opt+8, NULL, 0); + else { + int value = 1; + + if (!strncmp(this_opt, "no", 2)) { + value = 0; + this_opt += 2; + } + if (! strcmp(this_opt, "inverse")) + inverse = value; + else if (!strcmp(this_opt, "accel")) + noaccel = !value; + else if (!strcmp(this_opt, "pan")) + nopan = !value; + else if (!strcmp(this_opt, "pciretry")) + no_pci_retry = !value; + else if (!strcmp(this_opt, "vga")) + novga = !value; + else if (!strcmp(this_opt, "bios")) + nobios = !value; + else if (!strcmp(this_opt, "init")) + noinit = !value; + else if (!strcmp(this_opt, "mtrr")) + mtrr = value; + else if (!strcmp(this_opt, "inv24")) + inv24 = value; + else if (!strcmp(this_opt, "cross4MB")) + cross4MB = value; + else if (!strcmp(this_opt, "grayscale")) + grayscale = value; + else if (!strcmp(this_opt, "dfp")) + dfp = value; + else { + strscpy(videomode, this_opt, sizeof(videomode)); + } + } + } + return 0; +} + +static int __initdata initialized = 0; + +static int __init matroxfb_init(void) +{ + char *option = NULL; + int err = 0; + + DBG(__func__) + + if (fb_get_options("matroxfb", &option)) + return -ENODEV; + matroxfb_setup(option); + + if (disabled) + return -ENXIO; + if (!initialized) { + initialized = 1; + err = matrox_init(); + } + hotplug = 1; + /* never return failure, user can hotplug matrox later... */ + return err; +} + +#else + +/* *************************** init module code **************************** */ + +MODULE_AUTHOR("(c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Accelerated FBDev driver for Matrox Millennium/Mystique/G100/G200/G400/G450/G550"); +MODULE_LICENSE("GPL"); + +module_param(mem, int, 0); +MODULE_PARM_DESC(mem, "Size of available memory in MB, KB or B (2,4,8,12,16MB, default=autodetect)"); +module_param(disabled, int, 0); +MODULE_PARM_DESC(disabled, "Disabled (0 or 1=disabled) (default=0)"); +module_param(noaccel, int, 0); +MODULE_PARM_DESC(noaccel, "Do not use accelerating engine (0 or 1=disabled) (default=0)"); +module_param(nopan, int, 0); +MODULE_PARM_DESC(nopan, "Disable pan on startup (0 or 1=disabled) (default=0)"); +module_param(no_pci_retry, int, 0); +MODULE_PARM_DESC(no_pci_retry, "PCI retries enabled (0 or 1=disabled) (default=0)"); +module_param(novga, int, 0); +MODULE_PARM_DESC(novga, "VGA I/O (0x3C0-0x3DF) disabled (0 or 1=disabled) (default=0)"); +module_param(nobios, int, 0); +MODULE_PARM_DESC(nobios, "Disables ROM BIOS (0 or 1=disabled) (default=do not change BIOS state)"); +module_param(noinit, int, 0); +MODULE_PARM_DESC(noinit, "Disables W/SG/SD-RAM and bus interface initialization (0 or 1=do not initialize) (default=0)"); +module_param(memtype, int, 0); +MODULE_PARM_DESC(memtype, "Memory type for G200/G400 (see Documentation/fb/matroxfb.rst for explanation) (default=3 for G200, 0 for G400)"); +module_param(mtrr, int, 0); +MODULE_PARM_DESC(mtrr, "This speeds up video memory accesses (0=disabled or 1) (default=1)"); +module_param(sgram, int, 0); +MODULE_PARM_DESC(sgram, "Indicates that G100/G200/G400 has SGRAM memory (0=SDRAM, 1=SGRAM) (default=0)"); +module_param(inv24, int, 0); +MODULE_PARM_DESC(inv24, "Inverts clock polarity for 24bpp and loop frequency > 100MHz (default=do not invert polarity)"); +module_param(inverse, int, 0); +MODULE_PARM_DESC(inverse, "Inverse (0 or 1) (default=0)"); +module_param(dev, int, 0); +MODULE_PARM_DESC(dev, "Multihead support, attach to device ID (0..N) (default=all working)"); +module_param(vesa, int, 0); +MODULE_PARM_DESC(vesa, "Startup videomode (0x000-0x1FF) (default=0x101)"); +module_param(xres, int, 0); +MODULE_PARM_DESC(xres, "Horizontal resolution (px), overrides xres from vesa (default=vesa)"); +module_param(yres, int, 0); +MODULE_PARM_DESC(yres, "Vertical resolution (scans), overrides yres from vesa (default=vesa)"); +module_param(upper, int, 0); +MODULE_PARM_DESC(upper, "Upper blank space (scans), overrides upper from vesa (default=vesa)"); +module_param(lower, int, 0); +MODULE_PARM_DESC(lower, "Lower blank space (scans), overrides lower from vesa (default=vesa)"); +module_param(vslen, int, 0); +MODULE_PARM_DESC(vslen, "Vertical sync length (scans), overrides lower from vesa (default=vesa)"); +module_param(left, int, 0); +MODULE_PARM_DESC(left, "Left blank space (px), overrides left from vesa (default=vesa)"); +module_param(right, int, 0); +MODULE_PARM_DESC(right, "Right blank space (px), overrides right from vesa (default=vesa)"); +module_param(hslen, int, 0); +MODULE_PARM_DESC(hslen, "Horizontal sync length (px), overrides hslen from vesa (default=vesa)"); +module_param(pixclock, int, 0); +MODULE_PARM_DESC(pixclock, "Pixelclock (ns), overrides pixclock from vesa (default=vesa)"); +module_param(sync, int, 0); +MODULE_PARM_DESC(sync, "Sync polarity, overrides sync from vesa (default=vesa)"); +module_param(depth, int, 0); +MODULE_PARM_DESC(depth, "Color depth (0=text,8,15,16,24,32) (default=vesa)"); +module_param(maxclk, int, 0); +MODULE_PARM_DESC(maxclk, "Startup maximal clock, 0-999MHz, 1000-999999kHz, 1000000-INF Hz"); +module_param(fh, int, 0); +MODULE_PARM_DESC(fh, "Startup horizontal frequency, 0-999kHz, 1000-INF Hz"); +module_param(fv, int, 0); +MODULE_PARM_DESC(fv, "Startup vertical frequency, 0-INF Hz\n" +"You should specify \"fv:max_monitor_vsync,fh:max_monitor_hsync,maxclk:max_monitor_dotclock\""); +module_param(grayscale, int, 0); +MODULE_PARM_DESC(grayscale, "Sets display into grayscale. Works perfectly with paletized videomode (4, 8bpp), some limitations apply to 16, 24 and 32bpp videomodes (default=nograyscale)"); +module_param(cross4MB, int, 0); +MODULE_PARM_DESC(cross4MB, "Specifies that 4MB boundary can be in middle of line. (default=autodetected)"); +module_param(dfp, int, 0); +MODULE_PARM_DESC(dfp, "Specifies whether to use digital flat panel interface of G200/G400 (0 or 1) (default=0)"); +module_param(dfp_type, int, 0); +MODULE_PARM_DESC(dfp_type, "Specifies DFP interface type (0 to 255) (default=read from hardware)"); +module_param_string(outputs, outputs, sizeof(outputs), 0); +MODULE_PARM_DESC(outputs, "Specifies which CRTC is mapped to which output (string of up to three letters, consisting of 0 (disabled), 1 (CRTC1), 2 (CRTC2)) (default=111 for Gx50, 101 for G200/G400 with DFP, and 100 for all other devices)"); +#ifdef CONFIG_PPC_PMAC +module_param_named(vmode, default_vmode, int, 0); +MODULE_PARM_DESC(vmode, "Specify the vmode mode number that should be used (640x480 default)"); +module_param_named(cmode, default_cmode, int, 0); +MODULE_PARM_DESC(cmode, "Specify the video depth that should be used (8bit default)"); +#endif + +static int __init matroxfb_init(void){ + + DBG(__func__) + + if (disabled) + return -ENXIO; + + if (depth == 0) + depth = RSText; + else if (depth == 4) + depth = RS4bpp; + else if (depth == 8) + depth = RS8bpp; + else if (depth == 15) + depth = RS15bpp; + else if (depth == 16) + depth = RS16bpp; + else if (depth == 24) + depth = RS24bpp; + else if (depth == 32) + depth = RS32bpp; + else if (depth != -1) { + printk(KERN_ERR "matroxfb: depth %d is not supported, using default\n", depth); + depth = -1; + } + matrox_init(); + /* never return failure; user can hotplug matrox later... */ + return 0; +} +#endif /* MODULE */ + +module_init(matroxfb_init); +module_exit(matrox_done); +EXPORT_SYMBOL(matroxfb_register_driver); +EXPORT_SYMBOL(matroxfb_unregister_driver); +EXPORT_SYMBOL(matroxfb_wait_for_sync); +EXPORT_SYMBOL(matroxfb_enable_irq); diff --git a/drivers/video/fbdev/matrox/matroxfb_base.h b/drivers/video/fbdev/matrox/matroxfb_base.h new file mode 100644 index 000000000..a6437c40f --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_base.h @@ -0,0 +1,709 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + */ +#ifndef __MATROXFB_H__ +#define __MATROXFB_H__ + +/* general, but fairly heavy, debugging */ +#undef MATROXFB_DEBUG + +/* heavy debugging: */ +/* -- logs putc[s], so every time a char is displayed, it's logged */ +#undef MATROXFB_DEBUG_HEAVY + +/* This one _could_ cause infinite loops */ +/* It _does_ cause lots and lots of messages during idle loops */ +#undef MATROXFB_DEBUG_LOOP + +/* Debug register calls, too? */ +#undef MATROXFB_DEBUG_REG + +/* Guard accelerator accesses with spin_lock_irqsave... */ +#undef MATROXFB_USE_SPINLOCKS + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/selection.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <linux/kd.h> + +#include <asm/io.h> +#include <linux/unaligned.h> + +#if defined(CONFIG_PPC_PMAC) +#include "../macmodes.h" +#endif + +#ifdef MATROXFB_DEBUG + +#define DEBUG +#define DBG(x) printk(KERN_DEBUG "matroxfb: %s\n", (x)); + +#ifdef MATROXFB_DEBUG_HEAVY +#define DBG_HEAVY(x) DBG(x) +#else /* MATROXFB_DEBUG_HEAVY */ +#define DBG_HEAVY(x) /* DBG_HEAVY */ +#endif /* MATROXFB_DEBUG_HEAVY */ + +#ifdef MATROXFB_DEBUG_LOOP +#define DBG_LOOP(x) DBG(x) +#else /* MATROXFB_DEBUG_LOOP */ +#define DBG_LOOP(x) /* DBG_LOOP */ +#endif /* MATROXFB_DEBUG_LOOP */ + +#ifdef MATROXFB_DEBUG_REG +#define DBG_REG(x) DBG(x) +#else /* MATROXFB_DEBUG_REG */ +#define DBG_REG(x) /* DBG_REG */ +#endif /* MATROXFB_DEBUG_REG */ + +#else /* MATROXFB_DEBUG */ + +#define DBG(x) /* DBG */ +#define DBG_HEAVY(x) /* DBG_HEAVY */ +#define DBG_REG(x) /* DBG_REG */ +#define DBG_LOOP(x) /* DBG_LOOP */ + +#endif /* MATROXFB_DEBUG */ + +#ifdef DEBUG +#define dprintk(X...) printk(X) +#else +#define dprintk(X...) no_printk(X) +#endif + +#ifndef PCI_SS_VENDOR_ID_SIEMENS_NIXDORF +#define PCI_SS_VENDOR_ID_SIEMENS_NIXDORF 0x110A +#endif +#ifndef PCI_SS_VENDOR_ID_MATROX +#define PCI_SS_VENDOR_ID_MATROX PCI_VENDOR_ID_MATROX +#endif + +#ifndef PCI_SS_ID_MATROX_PRODUCTIVA_G100_AGP +#define PCI_SS_ID_MATROX_GENERIC 0xFF00 +#define PCI_SS_ID_MATROX_PRODUCTIVA_G100_AGP 0xFF01 +#define PCI_SS_ID_MATROX_MYSTIQUE_G200_AGP 0xFF02 +#define PCI_SS_ID_MATROX_MILLENIUM_G200_AGP 0xFF03 +#define PCI_SS_ID_MATROX_MARVEL_G200_AGP 0xFF04 +#define PCI_SS_ID_MATROX_MGA_G100_PCI 0xFF05 +#define PCI_SS_ID_MATROX_MGA_G100_AGP 0x1001 +#define PCI_SS_ID_MATROX_MILLENNIUM_G400_MAX_AGP 0x2179 +#define PCI_SS_ID_SIEMENS_MGA_G100_AGP 0x001E /* 30 */ +#define PCI_SS_ID_SIEMENS_MGA_G200_AGP 0x0032 /* 50 */ +#endif + +#define MX_VISUAL_TRUECOLOR FB_VISUAL_DIRECTCOLOR +#define MX_VISUAL_DIRECTCOLOR FB_VISUAL_TRUECOLOR +#define MX_VISUAL_PSEUDOCOLOR FB_VISUAL_PSEUDOCOLOR + +#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16) + +/* G-series and Mystique have (almost) same DAC */ +#undef NEED_DAC1064 +#if defined(CONFIG_FB_MATROX_MYSTIQUE) || defined(CONFIG_FB_MATROX_G) +#define NEED_DAC1064 1 +#endif + +typedef struct { + void __iomem* vaddr; +} vaddr_t; + +static inline unsigned int mga_readb(vaddr_t va, unsigned int offs) { + return readb(va.vaddr + offs); +} + +static inline void mga_writeb(vaddr_t va, unsigned int offs, u_int8_t value) { + writeb(value, va.vaddr + offs); +} + +static inline void mga_writew(vaddr_t va, unsigned int offs, u_int16_t value) { + writew(value, va.vaddr + offs); +} + +static inline u_int32_t mga_readl(vaddr_t va, unsigned int offs) { + return readl(va.vaddr + offs); +} + +static inline void mga_writel(vaddr_t va, unsigned int offs, u_int32_t value) { + writel(value, va.vaddr + offs); +} + +static inline void mga_memcpy_toio(vaddr_t va, const void* src, int len) { +#if defined(__alpha__) || defined(__i386__) || defined(__x86_64__) + /* + * iowrite32_rep works for us if: + * (1) Copies data as 32bit quantities, not byte after byte, + * (2) Performs LE ordered stores, and + * (3) It copes with unaligned source (destination is guaranteed to be page + * aligned and length is guaranteed to be multiple of 4). + */ + iowrite32_rep(va.vaddr, src, len >> 2); +#else + u_int32_t __iomem* addr = va.vaddr; + + if ((unsigned long)src & 3) { + while (len >= 4) { + fb_writel(get_unaligned((u32 *)src), addr); + addr++; + len -= 4; + src += 4; + } + } else { + while (len >= 4) { + fb_writel(*(u32 *)src, addr); + addr++; + len -= 4; + src += 4; + } + } +#endif +} + +static inline void vaddr_add(vaddr_t* va, unsigned long offs) { + va->vaddr += offs; +} + +static inline void __iomem* vaddr_va(vaddr_t va) { + return va.vaddr; +} + +struct my_timming { + unsigned int pixclock; + int mnp; + unsigned int crtc; + unsigned int HDisplay; + unsigned int HSyncStart; + unsigned int HSyncEnd; + unsigned int HTotal; + unsigned int VDisplay; + unsigned int VSyncStart; + unsigned int VSyncEnd; + unsigned int VTotal; + unsigned int sync; + int dblscan; + int interlaced; + unsigned int delay; /* CRTC delay */ +}; + +enum { M_SYSTEM_PLL, M_PIXEL_PLL_A, M_PIXEL_PLL_B, M_PIXEL_PLL_C, M_VIDEO_PLL }; + +struct matrox_pll_cache { + unsigned int valid; + struct { + unsigned int mnp_key; + unsigned int mnp_value; + } data[4]; +}; + +struct matrox_pll_limits { + unsigned int vcomin; + unsigned int vcomax; +}; + +struct matrox_pll_features { + unsigned int vco_freq_min; + unsigned int ref_freq; + unsigned int feed_div_min; + unsigned int feed_div_max; + unsigned int in_div_min; + unsigned int in_div_max; + unsigned int post_shift_max; +}; + +struct matroxfb_par +{ + unsigned int final_bppShift; + unsigned int cmap_len; + struct { + unsigned int bytes; + unsigned int pixels; + unsigned int chunks; + } ydstorg; +}; + +struct matrox_fb_info; + +struct matrox_DAC1064_features { + u_int8_t xvrefctrl; + u_int8_t xmiscctrl; +}; + +/* current hardware status */ +struct mavenregs { + u_int8_t regs[256]; + int mode; + int vlines; + int xtal; + int fv; + + u_int16_t htotal; + u_int16_t hcorr; +}; + +struct matrox_crtc2 { + u_int32_t ctl; +}; + +struct matrox_hw_state { + u_int32_t MXoptionReg; + unsigned char DACclk[6]; + unsigned char DACreg[80]; + unsigned char MiscOutReg; + unsigned char DACpal[768]; + unsigned char CRTC[25]; + unsigned char CRTCEXT[9]; + unsigned char SEQ[5]; + /* unused for MGA mode, but who knows... */ + unsigned char GCTL[9]; + /* unused for MGA mode, but who knows... */ + unsigned char ATTR[21]; + + /* TVOut only */ + struct mavenregs maven; + + struct matrox_crtc2 crtc2; +}; + +struct matrox_accel_data { +#ifdef CONFIG_FB_MATROX_MILLENIUM + unsigned char ramdac_rev; +#endif + u_int32_t m_dwg_rect; + u_int32_t m_opmode; + u_int32_t m_access; + u_int32_t m_pitch; +}; + +struct v4l2_queryctrl; +struct v4l2_control; + +struct matrox_altout { + const char *name; + int (*compute)(void* altout_dev, struct my_timming* input); + int (*program)(void* altout_dev); + int (*start)(void* altout_dev); + int (*verifymode)(void* altout_dev, u_int32_t mode); + int (*getqueryctrl)(void* altout_dev, + struct v4l2_queryctrl* ctrl); + int (*getctrl)(void *altout_dev, + struct v4l2_control* ctrl); + int (*setctrl)(void *altout_dev, + struct v4l2_control* ctrl); +}; + +#define MATROXFB_SRC_NONE 0 +#define MATROXFB_SRC_CRTC1 1 +#define MATROXFB_SRC_CRTC2 2 + +enum mga_chip { MGA_2064, MGA_2164, MGA_1064, MGA_1164, MGA_G100, MGA_G200, MGA_G400, MGA_G450, MGA_G550 }; + +struct matrox_bios { + unsigned int bios_valid : 1; + unsigned int pins_len; + unsigned char pins[128]; + struct { + unsigned char vMaj, vMin, vRev; + } version; + struct { + unsigned char state, tvout; + } output; +}; + +struct matrox_switch; +struct matroxfb_driver; +struct matroxfb_dh_fb_info; + +struct matrox_vsync { + wait_queue_head_t wait; + unsigned int cnt; +}; + +struct matrox_fb_info { + struct fb_info fbcon; + + struct list_head next_fb; + + int dead; + int initialized; + unsigned int usecount; + + unsigned int userusecount; + unsigned long irq_flags; + + struct matroxfb_par curr; + struct matrox_hw_state hw; + + struct matrox_accel_data accel; + + struct pci_dev* pcidev; + + struct { + struct matrox_vsync vsync; + unsigned int pixclock; + int mnp; + int panpos; + } crtc1; + struct { + struct matrox_vsync vsync; + unsigned int pixclock; + int mnp; + struct matroxfb_dh_fb_info* info; + struct rw_semaphore lock; + } crtc2; + struct { + struct rw_semaphore lock; + struct { + int brightness, contrast, saturation, hue, gamma; + int testout, deflicker; + } tvo_params; + } altout; +#define MATROXFB_MAX_OUTPUTS 3 + struct { + unsigned int src; + struct matrox_altout* output; + void* data; + unsigned int mode; + unsigned int default_src; + } outputs[MATROXFB_MAX_OUTPUTS]; + +#define MATROXFB_MAX_FB_DRIVERS 5 + struct matroxfb_driver* (drivers[MATROXFB_MAX_FB_DRIVERS]); + void* (drivers_data[MATROXFB_MAX_FB_DRIVERS]); + unsigned int drivers_count; + + struct { + unsigned long base; /* physical */ + vaddr_t vbase; /* CPU view */ + unsigned int len; + unsigned int len_usable; + unsigned int len_maximum; + } video; + + struct { + unsigned long base; /* physical */ + vaddr_t vbase; /* CPU view */ + unsigned int len; + } mmio; + + unsigned int max_pixel_clock; + unsigned int max_pixel_clock_panellink; + + struct matrox_switch* hw_switch; + + struct { + struct matrox_pll_features pll; + struct matrox_DAC1064_features DAC1064; + } features; + struct { + spinlock_t DAC; + spinlock_t accel; + } lock; + + enum mga_chip chip; + + int interleave; + int millenium; + int milleniumII; + struct { + int cfb4; + const int* vxres; + int cross4MB; + int text; + int plnwt; + int srcorg; + } capable; + int wc_cookie; + struct { + int precise_width; + int mga_24bpp_fix; + int novga; + int nobios; + int nopciretry; + int noinit; + int sgram; + int support32MB; + + int accelerator; + int text_type_aux; + int video64bits; + int crtc2; + int maven_capable; + unsigned int vgastep; + unsigned int textmode; + unsigned int textstep; + unsigned int textvram; /* character cells */ + unsigned int ydstorg; /* offset in bytes from video start to usable memory */ + /* 0 except for 6MB Millenium */ + int memtype; + int g450dac; + int dfp_type; + int panellink; /* G400 DFP possible (not G450/G550) */ + int dualhead; + unsigned int fbResource; + } devflags; + struct fb_ops fbops; + struct matrox_bios bios; + struct { + struct matrox_pll_limits pixel; + struct matrox_pll_limits system; + struct matrox_pll_limits video; + } limits; + struct { + struct matrox_pll_cache pixel; + struct matrox_pll_cache system; + struct matrox_pll_cache video; + } cache; + struct { + struct { + unsigned int video; + unsigned int system; + } pll; + struct { + u_int32_t opt; + u_int32_t opt2; + u_int32_t opt3; + u_int32_t mctlwtst; + u_int32_t mctlwtst_core; + u_int32_t memmisc; + u_int32_t memrdbk; + u_int32_t maccess; + } reg; + struct { + unsigned int ddr:1, + emrswen:1, + dll:1; + } memory; + } values; + u_int32_t cmap[16]; +}; + +#define info2minfo(info) container_of(info, struct matrox_fb_info, fbcon) + +struct matrox_switch { + int (*preinit)(struct matrox_fb_info *minfo); + void (*reset)(struct matrox_fb_info *minfo); + int (*init)(struct matrox_fb_info *minfo, struct my_timming*); + void (*restore)(struct matrox_fb_info *minfo); +}; + +struct matroxfb_driver { + struct list_head node; + char* name; + void* (*probe)(struct matrox_fb_info* info); + void (*remove)(struct matrox_fb_info* info, void* data); +}; + +int matroxfb_register_driver(struct matroxfb_driver* drv); +void matroxfb_unregister_driver(struct matroxfb_driver* drv); + +#define PCI_OPTION_REG 0x40 +#define PCI_OPTION_ENABLE_ROM 0x40000000 + +#define PCI_MGA_INDEX 0x44 +#define PCI_MGA_DATA 0x48 +#define PCI_OPTION2_REG 0x50 +#define PCI_OPTION3_REG 0x54 +#define PCI_MEMMISC_REG 0x58 + +#define M_DWGCTL 0x1C00 +#define M_MACCESS 0x1C04 +#define M_CTLWTST 0x1C08 + +#define M_PLNWT 0x1C1C + +#define M_BCOL 0x1C20 +#define M_FCOL 0x1C24 + +#define M_SGN 0x1C58 +#define M_LEN 0x1C5C +#define M_AR0 0x1C60 +#define M_AR1 0x1C64 +#define M_AR2 0x1C68 +#define M_AR3 0x1C6C +#define M_AR4 0x1C70 +#define M_AR5 0x1C74 +#define M_AR6 0x1C78 + +#define M_CXBNDRY 0x1C80 +#define M_FXBNDRY 0x1C84 +#define M_YDSTLEN 0x1C88 +#define M_PITCH 0x1C8C +#define M_YDST 0x1C90 +#define M_YDSTORG 0x1C94 +#define M_YTOP 0x1C98 +#define M_YBOT 0x1C9C + +/* mystique only */ +#define M_CACHEFLUSH 0x1FFF + +#define M_EXEC 0x0100 + +#define M_DWG_TRAP 0x04 +#define M_DWG_BITBLT 0x08 +#define M_DWG_ILOAD 0x09 + +#define M_DWG_LINEAR 0x0080 +#define M_DWG_SOLID 0x0800 +#define M_DWG_ARZERO 0x1000 +#define M_DWG_SGNZERO 0x2000 +#define M_DWG_SHIFTZERO 0x4000 + +#define M_DWG_REPLACE 0x000C0000 +#define M_DWG_REPLACE2 (M_DWG_REPLACE | 0x40) +#define M_DWG_XOR 0x00060010 + +#define M_DWG_BFCOL 0x04000000 +#define M_DWG_BMONOWF 0x08000000 + +#define M_DWG_TRANSC 0x40000000 + +#define M_FIFOSTATUS 0x1E10 +#define M_STATUS 0x1E14 +#define M_ICLEAR 0x1E18 +#define M_IEN 0x1E1C + +#define M_VCOUNT 0x1E20 + +#define M_RESET 0x1E40 +#define M_MEMRDBK 0x1E44 + +#define M_AGP2PLL 0x1E4C + +#define M_OPMODE 0x1E54 +#define M_OPMODE_DMA_GEN_WRITE 0x00 +#define M_OPMODE_DMA_BLIT 0x04 +#define M_OPMODE_DMA_VECTOR_WRITE 0x08 +#define M_OPMODE_DMA_LE 0x0000 /* little endian - no transformation */ +#define M_OPMODE_DMA_BE_8BPP 0x0000 +#define M_OPMODE_DMA_BE_16BPP 0x0100 +#define M_OPMODE_DMA_BE_32BPP 0x0200 +#define M_OPMODE_DIR_LE 0x000000 /* little endian - no transformation */ +#define M_OPMODE_DIR_BE_8BPP 0x000000 +#define M_OPMODE_DIR_BE_16BPP 0x010000 +#define M_OPMODE_DIR_BE_32BPP 0x020000 + +#define M_ATTR_INDEX 0x1FC0 +#define M_ATTR_DATA 0x1FC1 + +#define M_MISC_REG 0x1FC2 +#define M_3C2_RD 0x1FC2 + +#define M_SEQ_INDEX 0x1FC4 +#define M_SEQ_DATA 0x1FC5 +#define M_SEQ1 0x01 +#define M_SEQ1_SCROFF 0x20 + +#define M_MISC_REG_READ 0x1FCC + +#define M_GRAPHICS_INDEX 0x1FCE +#define M_GRAPHICS_DATA 0x1FCF + +#define M_CRTC_INDEX 0x1FD4 + +#define M_ATTR_RESET 0x1FDA +#define M_3DA_WR 0x1FDA +#define M_INSTS1 0x1FDA + +#define M_EXTVGA_INDEX 0x1FDE +#define M_EXTVGA_DATA 0x1FDF + +/* G200 only */ +#define M_SRCORG 0x2CB4 +#define M_DSTORG 0x2CB8 + +#define M_RAMDAC_BASE 0x3C00 + +/* fortunately, same on TVP3026 and MGA1064 */ +#define M_DAC_REG (M_RAMDAC_BASE+0) +#define M_DAC_VAL (M_RAMDAC_BASE+1) +#define M_PALETTE_MASK (M_RAMDAC_BASE+2) + +#define M_X_INDEX 0x00 +#define M_X_DATAREG 0x0A + +#define DAC_XGENIOCTRL 0x2A +#define DAC_XGENIODATA 0x2B + +#define M_C2CTL 0x3C10 + +#define MX_OPTION_BSWAP 0x00000000 + +#ifdef __LITTLE_ENDIAN +#define M_OPMODE_4BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#define M_OPMODE_8BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#define M_OPMODE_16BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#define M_OPMODE_24BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#define M_OPMODE_32BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) +#else +#ifdef __BIG_ENDIAN +#define M_OPMODE_4BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_LE | M_OPMODE_DMA_BLIT) /* TODO */ +#define M_OPMODE_8BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_BE_8BPP | M_OPMODE_DMA_BLIT) +#define M_OPMODE_16BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_BE_16BPP | M_OPMODE_DMA_BLIT) +#define M_OPMODE_24BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_BE_8BPP | M_OPMODE_DMA_BLIT) /* TODO, ?32 */ +#define M_OPMODE_32BPP (M_OPMODE_DMA_LE | M_OPMODE_DIR_BE_32BPP | M_OPMODE_DMA_BLIT) +#else +#error "Byte ordering have to be defined. Cannot continue." +#endif +#endif + +#define mga_inb(addr) mga_readb(minfo->mmio.vbase, (addr)) +#define mga_inl(addr) mga_readl(minfo->mmio.vbase, (addr)) +#define mga_outb(addr,val) mga_writeb(minfo->mmio.vbase, (addr), (val)) +#define mga_outw(addr,val) mga_writew(minfo->mmio.vbase, (addr), (val)) +#define mga_outl(addr,val) mga_writel(minfo->mmio.vbase, (addr), (val)) +#define mga_readr(port,idx) (mga_outb((port),(idx)), mga_inb((port)+1)) +#define mga_setr(addr,port,val) mga_outw(addr, ((val)<<8) | (port)) + +#define mga_fifo(n) do {} while ((mga_inl(M_FIFOSTATUS) & 0xFF) < (n)) + +#define WaitTillIdle() do { mga_inl(M_STATUS); do {} while (mga_inl(M_STATUS) & 0x10000); } while (0) + +/* code speedup */ +#ifdef CONFIG_FB_MATROX_MILLENIUM +#define isInterleave(x) (x->interleave) +#define isMillenium(x) (x->millenium) +#define isMilleniumII(x) (x->milleniumII) +#else +#define isInterleave(x) (0) +#define isMillenium(x) (0) +#define isMilleniumII(x) (0) +#endif + +#define matroxfb_DAC_lock() spin_lock(&minfo->lock.DAC) +#define matroxfb_DAC_unlock() spin_unlock(&minfo->lock.DAC) +#define matroxfb_DAC_lock_irqsave(flags) spin_lock_irqsave(&minfo->lock.DAC, flags) +#define matroxfb_DAC_unlock_irqrestore(flags) spin_unlock_irqrestore(&minfo->lock.DAC, flags) +extern void matroxfb_DAC_out(const struct matrox_fb_info *minfo, int reg, + int val); +extern int matroxfb_DAC_in(const struct matrox_fb_info *minfo, int reg); +extern void matroxfb_var2my(struct fb_var_screeninfo* fvsi, struct my_timming* mt); +extern int matroxfb_wait_for_sync(struct matrox_fb_info *minfo, u_int32_t crtc); +extern int matroxfb_enable_irq(struct matrox_fb_info *minfo, int reenable); + +#ifdef MATROXFB_USE_SPINLOCKS +#define CRITBEGIN spin_lock_irqsave(&minfo->lock.accel, critflags); +#define CRITEND spin_unlock_irqrestore(&minfo->lock.accel, critflags); +#define CRITFLAGS unsigned long critflags; +#else +#define CRITBEGIN +#define CRITEND +#define CRITFLAGS +#endif + +#endif /* __MATROXFB_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_crtc2.c b/drivers/video/fbdev/matrox/matroxfb_crtc2.c new file mode 100644 index 000000000..8de882b09 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_crtc2.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + */ + +#include "matroxfb_maven.h" +#include "matroxfb_crtc2.h" +#include "matroxfb_misc.h" +#include "matroxfb_DAC1064.h" +#include <linux/matroxfb.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +/* **************************************************** */ + +static int mem = 8192; + +module_param(mem, int, 0); +MODULE_PARM_DESC(mem, "Memory size reserved for dualhead (default=8MB)"); + +/* **************************************************** */ + +static int matroxfb_dh_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info* info) { + u_int32_t col; +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + + if (regno >= 16) + return 1; + if (m2info->fbcon.var.grayscale) { + /* gray = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + red = CNVT_TOHW(red, m2info->fbcon.var.red.length); + green = CNVT_TOHW(green, m2info->fbcon.var.green.length); + blue = CNVT_TOHW(blue, m2info->fbcon.var.blue.length); + transp = CNVT_TOHW(transp, m2info->fbcon.var.transp.length); + + col = (red << m2info->fbcon.var.red.offset) | + (green << m2info->fbcon.var.green.offset) | + (blue << m2info->fbcon.var.blue.offset) | + (transp << m2info->fbcon.var.transp.offset); + + switch (m2info->fbcon.var.bits_per_pixel) { + case 16: + m2info->cmap[regno] = col | (col << 16); + break; + case 32: + m2info->cmap[regno] = col; + break; + } + return 0; +#undef m2info +} + +static void matroxfb_dh_restore(struct matroxfb_dh_fb_info* m2info, + struct my_timming* mt, + int mode, + unsigned int pos) { + u_int32_t tmp; + u_int32_t datactl; + struct matrox_fb_info *minfo = m2info->primary_dev; + + switch (mode) { + case 15: + tmp = 0x00200000; + break; + case 16: + tmp = 0x00400000; + break; +/* case 32: */ + default: + tmp = 0x00800000; + break; + } + tmp |= 0x00000001; /* enable CRTC2 */ + datactl = 0; + if (minfo->outputs[1].src == MATROXFB_SRC_CRTC2) { + if (minfo->devflags.g450dac) { + tmp |= 0x00000006; /* source from secondary pixel PLL */ + /* no vidrst when in monitor mode */ + if (minfo->outputs[1].mode != MATROXFB_OUTPUT_MODE_MONITOR) { + tmp |= 0xC0001000; /* Enable H/V vidrst */ + } + } else { + tmp |= 0x00000002; /* source from VDOCLK */ + tmp |= 0xC0000000; /* enable vvidrst & hvidrst */ + /* MGA TVO is our clock source */ + } + } else if (minfo->outputs[0].src == MATROXFB_SRC_CRTC2) { + tmp |= 0x00000004; /* source from pixclock */ + /* PIXPLL is our clock source */ + } + if (minfo->outputs[0].src == MATROXFB_SRC_CRTC2) { + tmp |= 0x00100000; /* connect CRTC2 to DAC */ + } + if (mt->interlaced) { + tmp |= 0x02000000; /* interlaced, second field is bigger, as G450 apparently ignores it */ + mt->VDisplay >>= 1; + mt->VSyncStart >>= 1; + mt->VSyncEnd >>= 1; + mt->VTotal >>= 1; + } + if ((mt->HTotal & 7) == 2) { + datactl |= 0x00000010; + mt->HTotal &= ~7; + } + tmp |= 0x10000000; /* 0x10000000 is VIDRST polarity */ + mga_outl(0x3C14, ((mt->HDisplay - 8) << 16) | (mt->HTotal - 8)); + mga_outl(0x3C18, ((mt->HSyncEnd - 8) << 16) | (mt->HSyncStart - 8)); + mga_outl(0x3C1C, ((mt->VDisplay - 1) << 16) | (mt->VTotal - 1)); + mga_outl(0x3C20, ((mt->VSyncEnd - 1) << 16) | (mt->VSyncStart - 1)); + mga_outl(0x3C24, ((mt->VSyncStart) << 16) | (mt->HSyncStart)); /* preload */ + { + u_int32_t linelen = m2info->fbcon.var.xres_virtual * (m2info->fbcon.var.bits_per_pixel >> 3); + if (tmp & 0x02000000) { + /* field #0 is smaller, so... */ + mga_outl(0x3C2C, pos); /* field #1 vmemory start */ + mga_outl(0x3C28, pos + linelen); /* field #0 vmemory start */ + linelen <<= 1; + m2info->interlaced = 1; + } else { + mga_outl(0x3C28, pos); /* vmemory start */ + m2info->interlaced = 0; + } + mga_outl(0x3C40, linelen); + } + mga_outl(0x3C4C, datactl); /* data control */ + if (tmp & 0x02000000) { + int i; + + mga_outl(0x3C10, tmp & ~0x02000000); + for (i = 0; i < 2; i++) { + unsigned int nl; + unsigned int lastl = 0; + + while ((nl = mga_inl(0x3C48) & 0xFFF) >= lastl) { + lastl = nl; + } + } + } + mga_outl(0x3C10, tmp); + minfo->hw.crtc2.ctl = tmp; + + tmp = mt->VDisplay << 16; /* line compare */ + if (mt->sync & FB_SYNC_HOR_HIGH_ACT) + tmp |= 0x00000100; + if (mt->sync & FB_SYNC_VERT_HIGH_ACT) + tmp |= 0x00000200; + mga_outl(0x3C44, tmp); +} + +static void matroxfb_dh_disable(struct matroxfb_dh_fb_info* m2info) { + struct matrox_fb_info *minfo = m2info->primary_dev; + + mga_outl(0x3C10, 0x00000004); /* disable CRTC2, CRTC1->DAC1, PLL as clock source */ + minfo->hw.crtc2.ctl = 0x00000004; +} + +static void matroxfb_dh_pan_var(struct matroxfb_dh_fb_info* m2info, + struct fb_var_screeninfo* var) { + unsigned int pos; + unsigned int linelen; + unsigned int pixelsize; + struct matrox_fb_info *minfo = m2info->primary_dev; + + m2info->fbcon.var.xoffset = var->xoffset; + m2info->fbcon.var.yoffset = var->yoffset; + pixelsize = m2info->fbcon.var.bits_per_pixel >> 3; + linelen = m2info->fbcon.var.xres_virtual * pixelsize; + pos = m2info->fbcon.var.yoffset * linelen + m2info->fbcon.var.xoffset * pixelsize; + pos += m2info->video.offbase; + if (m2info->interlaced) { + mga_outl(0x3C2C, pos); + mga_outl(0x3C28, pos + linelen); + } else { + mga_outl(0x3C28, pos); + } +} + +static int matroxfb_dh_decode_var(struct matroxfb_dh_fb_info* m2info, + struct fb_var_screeninfo* var, + int *visual, + int *video_cmap_len, + int *mode) { + unsigned int mask; + unsigned int memlen; + unsigned int vramlen; + + switch (var->bits_per_pixel) { + case 16: mask = 0x1F; + break; + case 32: mask = 0x0F; + break; + default: return -EINVAL; + } + vramlen = m2info->video.len_usable; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + var->xres_virtual = (var->xres_virtual + mask) & ~mask; + if (var->yres_virtual > 32767) + return -EINVAL; + memlen = var->xres_virtual * var->yres_virtual * (var->bits_per_pixel >> 3); + if (memlen > vramlen) + return -EINVAL; + if (var->xoffset + var->xres > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yoffset + var->yres > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + var->xres &= ~7; + var->left_margin &= ~7; + var->right_margin &= ~7; + var->hsync_len &= ~7; + + *mode = var->bits_per_pixel; + if (var->bits_per_pixel == 16) { + if (var->green.length == 5) { + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + *mode = 15; + } else { + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + } + } else { + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + } + *visual = FB_VISUAL_TRUECOLOR; + *video_cmap_len = 16; + return 0; +} + +static int matroxfb_dh_open(struct fb_info* info, int user) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + struct matrox_fb_info *minfo = m2info->primary_dev; + + if (minfo) { + int err; + + if (minfo->dead) { + return -ENXIO; + } + err = minfo->fbops.fb_open(&minfo->fbcon, user); + if (err) { + return err; + } + } + return 0; +#undef m2info +} + +static int matroxfb_dh_release(struct fb_info* info, int user) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + int err = 0; + struct matrox_fb_info *minfo = m2info->primary_dev; + + if (minfo) { + err = minfo->fbops.fb_release(&minfo->fbcon, user); + } + return err; +#undef m2info +} + +/* + * This function is called before the register_framebuffer so + * no locking is needed. + */ +static void matroxfb_dh_init_fix(struct matroxfb_dh_fb_info *m2info) +{ + struct fb_fix_screeninfo *fix = &m2info->fbcon.fix; + + strcpy(fix->id, "MATROX DH"); + + fix->smem_start = m2info->video.base; + fix->smem_len = m2info->video.len_usable; + fix->ypanstep = 1; + fix->ywrapstep = 0; + fix->xpanstep = 8; /* TBD */ + fix->mmio_start = m2info->mmio.base; + fix->mmio_len = m2info->mmio.len; + fix->accel = 0; /* no accel... */ +} + +static int matroxfb_dh_check_var(struct fb_var_screeninfo* var, struct fb_info* info) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + int visual; + int cmap_len; + int mode; + + return matroxfb_dh_decode_var(m2info, var, &visual, &cmap_len, &mode); +#undef m2info +} + +static int matroxfb_dh_set_par(struct fb_info* info) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + int visual; + int cmap_len; + int mode; + int err; + struct fb_var_screeninfo* var = &info->var; + struct matrox_fb_info *minfo = m2info->primary_dev; + + if ((err = matroxfb_dh_decode_var(m2info, var, &visual, &cmap_len, &mode)) != 0) + return err; + /* cmap */ + { + m2info->fbcon.screen_base = vaddr_va(m2info->video.vbase); + m2info->fbcon.fix.visual = visual; + m2info->fbcon.fix.type = FB_TYPE_PACKED_PIXELS; + m2info->fbcon.fix.type_aux = 0; + m2info->fbcon.fix.line_length = (var->xres_virtual * var->bits_per_pixel) >> 3; + } + { + struct my_timming mt; + unsigned int pos; + int out; + int cnt; + + matroxfb_var2my(&m2info->fbcon.var, &mt); + mt.crtc = MATROXFB_SRC_CRTC2; + /* CRTC2 delay */ + mt.delay = 34; + + pos = (m2info->fbcon.var.yoffset * m2info->fbcon.var.xres_virtual + m2info->fbcon.var.xoffset) * m2info->fbcon.var.bits_per_pixel >> 3; + pos += m2info->video.offbase; + cnt = 0; + down_read(&minfo->altout.lock); + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { + cnt++; + if (minfo->outputs[out].output->compute) { + minfo->outputs[out].output->compute(minfo->outputs[out].data, &mt); + } + } + } + minfo->crtc2.pixclock = mt.pixclock; + minfo->crtc2.mnp = mt.mnp; + up_read(&minfo->altout.lock); + if (cnt) { + matroxfb_dh_restore(m2info, &mt, mode, pos); + } else { + matroxfb_dh_disable(m2info); + } + DAC1064_global_init(minfo); + DAC1064_global_restore(minfo); + down_read(&minfo->altout.lock); + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2 && + minfo->outputs[out].output->program) { + minfo->outputs[out].output->program(minfo->outputs[out].data); + } + } + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2 && + minfo->outputs[out].output->start) { + minfo->outputs[out].output->start(minfo->outputs[out].data); + } + } + up_read(&minfo->altout.lock); + } + m2info->initialized = 1; + return 0; +#undef m2info +} + +static int matroxfb_dh_pan_display(struct fb_var_screeninfo* var, struct fb_info* info) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + matroxfb_dh_pan_var(m2info, var); + return 0; +#undef m2info +} + +static int matroxfb_dh_get_vblank(const struct matroxfb_dh_fb_info* m2info, struct fb_vblank* vblank) { + struct matrox_fb_info *minfo = m2info->primary_dev; + + matroxfb_enable_irq(minfo, 0); + memset(vblank, 0, sizeof(*vblank)); + vblank->flags = FB_VBLANK_HAVE_VCOUNT | FB_VBLANK_HAVE_VBLANK; + /* mask out reserved bits + field number (odd/even) */ + vblank->vcount = mga_inl(0x3C48) & 0x000007FF; + /* compatibility stuff */ + if (vblank->vcount >= m2info->fbcon.var.yres) + vblank->flags |= FB_VBLANK_VBLANKING; + if (test_bit(0, &minfo->irq_flags)) { + vblank->flags |= FB_VBLANK_HAVE_COUNT; + /* Only one writer, aligned int value... + it should work without lock and without atomic_t */ + vblank->count = minfo->crtc2.vsync.cnt; + } + return 0; +} + +static int matroxfb_dh_ioctl(struct fb_info *info, + unsigned int cmd, + unsigned long arg) +{ +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + struct matrox_fb_info *minfo = m2info->primary_dev; + + DBG(__func__) + + switch (cmd) { + case FBIOGET_VBLANK: + { + struct fb_vblank vblank; + int err; + + err = matroxfb_dh_get_vblank(m2info, &vblank); + if (err) + return err; + if (copy_to_user((void __user *)arg, &vblank, sizeof(vblank))) + return -EFAULT; + return 0; + } + case FBIO_WAITFORVSYNC: + { + u_int32_t crt; + + if (get_user(crt, (u_int32_t __user *)arg)) + return -EFAULT; + + if (crt != 0) + return -ENODEV; + return matroxfb_wait_for_sync(minfo, 1); + } + case MATROXFB_SET_OUTPUT_MODE: + case MATROXFB_GET_OUTPUT_MODE: + case MATROXFB_GET_ALL_OUTPUTS: + { + return minfo->fbcon.fbops->fb_ioctl(&minfo->fbcon, cmd, arg); + } + case MATROXFB_SET_OUTPUT_CONNECTION: + { + u_int32_t tmp; + int out; + int changes; + + if (get_user(tmp, (u_int32_t __user *)arg)) + return -EFAULT; + for (out = 0; out < 32; out++) { + if (tmp & (1 << out)) { + if (out >= MATROXFB_MAX_OUTPUTS) + return -ENXIO; + if (!minfo->outputs[out].output) + return -ENXIO; + switch (minfo->outputs[out].src) { + case MATROXFB_SRC_NONE: + case MATROXFB_SRC_CRTC2: + break; + default: + return -EBUSY; + } + } + } + if (minfo->devflags.panellink) { + if (tmp & MATROXFB_OUTPUT_CONN_DFP) + return -EINVAL; + if ((minfo->outputs[2].src == MATROXFB_SRC_CRTC1) && tmp) + return -EBUSY; + } + changes = 0; + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (tmp & (1 << out)) { + if (minfo->outputs[out].src != MATROXFB_SRC_CRTC2) { + changes = 1; + minfo->outputs[out].src = MATROXFB_SRC_CRTC2; + } + } else if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { + changes = 1; + minfo->outputs[out].src = MATROXFB_SRC_NONE; + } + } + if (!changes) + return 0; + matroxfb_dh_set_par(info); + return 0; + } + case MATROXFB_GET_OUTPUT_CONNECTION: + { + u_int32_t conn = 0; + int out; + + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { + conn |= 1 << out; + } + } + if (put_user(conn, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + case MATROXFB_GET_AVAILABLE_OUTPUTS: + { + u_int32_t tmp = 0; + int out; + + for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { + if (minfo->outputs[out].output) { + switch (minfo->outputs[out].src) { + case MATROXFB_SRC_NONE: + case MATROXFB_SRC_CRTC2: + tmp |= 1 << out; + break; + } + } + } + if (minfo->devflags.panellink) { + tmp &= ~MATROXFB_OUTPUT_CONN_DFP; + if (minfo->outputs[2].src == MATROXFB_SRC_CRTC1) { + tmp = 0; + } + } + if (put_user(tmp, (u_int32_t __user *)arg)) + return -EFAULT; + return 0; + } + } + return -ENOTTY; +#undef m2info +} + +static int matroxfb_dh_blank(int blank, struct fb_info* info) { +#define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) + switch (blank) { + case 1: + case 2: + case 3: + case 4: + default:; + } + /* do something... */ + return 0; +#undef m2info +} + +static const struct fb_ops matroxfb_dh_ops = { + .owner = THIS_MODULE, + .fb_open = matroxfb_dh_open, + .fb_release = matroxfb_dh_release, + FB_DEFAULT_IOMEM_OPS, + .fb_check_var = matroxfb_dh_check_var, + .fb_set_par = matroxfb_dh_set_par, + .fb_setcolreg = matroxfb_dh_setcolreg, + .fb_pan_display =matroxfb_dh_pan_display, + .fb_blank = matroxfb_dh_blank, + .fb_ioctl = matroxfb_dh_ioctl, +}; + +static struct fb_var_screeninfo matroxfb_dh_defined = { + 640,480,640,480,/* W,H, virtual W,H */ + 0,0, /* offset */ + 32, /* depth */ + 0, /* gray */ + {0,0,0}, /* R */ + {0,0,0}, /* G */ + {0,0,0}, /* B */ + {0,0,0}, /* alpha */ + 0, /* nonstd */ + FB_ACTIVATE_NOW, + -1,-1, /* display size */ + 0, /* accel flags */ + 39721L,48L,16L,33L,10L, + 96L,2,0, /* no sync info */ + FB_VMODE_NONINTERLACED, +}; + +static int matroxfb_dh_regit(const struct matrox_fb_info *minfo, + struct matroxfb_dh_fb_info *m2info) +{ +#define minfo (m2info->primary_dev) + void* oldcrtc2; + + m2info->fbcon.fbops = &matroxfb_dh_ops; + m2info->fbcon.flags = FBINFO_HWACCEL_XPAN | + FBINFO_HWACCEL_YPAN; + m2info->fbcon.pseudo_palette = m2info->cmap; + fb_alloc_cmap(&m2info->fbcon.cmap, 256, 1); + + if (mem < 64) + mem *= 1024; + if (mem < 64*1024) + mem *= 1024; + mem &= ~0x00000FFF; /* PAGE_MASK? */ + if (minfo->video.len_usable + mem <= minfo->video.len) + m2info->video.offbase = minfo->video.len - mem; + else if (minfo->video.len < mem) { + return -ENOMEM; + } else { /* check yres on first head... */ + m2info->video.borrowed = mem; + minfo->video.len_usable -= mem; + m2info->video.offbase = minfo->video.len_usable; + } + m2info->video.base = minfo->video.base + m2info->video.offbase; + m2info->video.len = m2info->video.len_usable = m2info->video.len_maximum = mem; + m2info->video.vbase.vaddr = vaddr_va(minfo->video.vbase) + m2info->video.offbase; + m2info->mmio.base = minfo->mmio.base; + m2info->mmio.vbase = minfo->mmio.vbase; + m2info->mmio.len = minfo->mmio.len; + + matroxfb_dh_init_fix(m2info); + if (register_framebuffer(&m2info->fbcon)) { + return -ENXIO; + } + if (!m2info->initialized) + fb_set_var(&m2info->fbcon, &matroxfb_dh_defined); + down_write(&minfo->crtc2.lock); + oldcrtc2 = minfo->crtc2.info; + minfo->crtc2.info = m2info; + up_write(&minfo->crtc2.lock); + if (oldcrtc2) { + printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 already present: %p\n", + oldcrtc2); + } + return 0; +#undef minfo +} + +/* ************************** */ + +static int matroxfb_dh_registerfb(struct matroxfb_dh_fb_info* m2info) { +#define minfo (m2info->primary_dev) + if (matroxfb_dh_regit(minfo, m2info)) { + printk(KERN_ERR "matroxfb_crtc2: secondary head failed to register\n"); + return -1; + } + printk(KERN_INFO "matroxfb_crtc2: secondary head of fb%u was registered as fb%u\n", + minfo->fbcon.node, m2info->fbcon.node); + m2info->fbcon_registered = 1; + return 0; +#undef minfo +} + +static void matroxfb_dh_deregisterfb(struct matroxfb_dh_fb_info* m2info) { +#define minfo (m2info->primary_dev) + if (m2info->fbcon_registered) { + int id; + struct matroxfb_dh_fb_info* crtc2; + + down_write(&minfo->crtc2.lock); + crtc2 = minfo->crtc2.info; + if (crtc2 == m2info) + minfo->crtc2.info = NULL; + up_write(&minfo->crtc2.lock); + if (crtc2 != m2info) { + printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 mismatch at unload: %p != %p\n", + crtc2, m2info); + printk(KERN_ERR "matroxfb_crtc2: Expect kernel crash after module unload.\n"); + return; + } + id = m2info->fbcon.node; + unregister_framebuffer(&m2info->fbcon); + /* return memory back to primary head */ + minfo->video.len_usable += m2info->video.borrowed; + printk(KERN_INFO "matroxfb_crtc2: fb%u unregistered\n", id); + m2info->fbcon_registered = 0; + } +#undef minfo +} + +static void* matroxfb_crtc2_probe(struct matrox_fb_info* minfo) { + struct matroxfb_dh_fb_info* m2info; + + /* hardware is CRTC2 incapable... */ + if (!minfo->devflags.crtc2) + return NULL; + m2info = kzalloc_obj(*m2info); + if (!m2info) + return NULL; + + m2info->primary_dev = minfo; + if (matroxfb_dh_registerfb(m2info)) { + kfree(m2info); + printk(KERN_ERR "matroxfb_crtc2: CRTC2 framebuffer failed to register\n"); + return NULL; + } + return m2info; +} + +static void matroxfb_crtc2_remove(struct matrox_fb_info* minfo, void* crtc2) { + matroxfb_dh_deregisterfb(crtc2); + kfree(crtc2); +} + +static struct matroxfb_driver crtc2 = { + .name = "Matrox G400 CRTC2", + .probe = matroxfb_crtc2_probe, + .remove = matroxfb_crtc2_remove }; + +static int matroxfb_crtc2_init(void) { + if (fb_get_options("matrox_crtc2fb", NULL)) + return -ENODEV; + + matroxfb_register_driver(&crtc2); + return 0; +} + +static void matroxfb_crtc2_exit(void) { + matroxfb_unregister_driver(&crtc2); +} + +MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Matrox G400 CRTC2 driver"); +MODULE_LICENSE("GPL"); +module_init(matroxfb_crtc2_init); +module_exit(matroxfb_crtc2_exit); +/* we do not have __setup() yet */ diff --git a/drivers/video/fbdev/matrox/matroxfb_crtc2.h b/drivers/video/fbdev/matrox/matroxfb_crtc2.h new file mode 100644 index 000000000..23e90e210 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_crtc2.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MATROXFB_CRTC2_H__ +#define __MATROXFB_CRTC2_H__ + +#include <linux/ioctl.h> +#include "matroxfb_base.h" + +struct matroxfb_dh_fb_info { + struct fb_info fbcon; + int fbcon_registered; + int initialized; + + struct matrox_fb_info* primary_dev; + + struct { + unsigned long base; /* physical */ + vaddr_t vbase; /* virtual */ + unsigned int len; + unsigned int len_usable; + unsigned int len_maximum; + unsigned int offbase; + unsigned int borrowed; + } video; + struct { + unsigned long base; + vaddr_t vbase; + unsigned int len; + } mmio; + + unsigned int interlaced:1; + + u_int32_t cmap[16]; +}; + +#endif /* __MATROXFB_CRTC2_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_g450.c b/drivers/video/fbdev/matrox/matroxfb_g450.c new file mode 100644 index 000000000..800c05b70 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_g450.c @@ -0,0 +1,643 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * See matroxfb_base.c for contributors. + * + */ + +#include <linux/export.h> + +#include "matroxfb_base.h" +#include "matroxfb_misc.h" +#include "matroxfb_DAC1064.h" +#include "g450_pll.h" +#include <linux/matroxfb.h> +#include <asm/div64.h> + +#include "matroxfb_g450.h" + +/* Definition of the various controls */ +struct mctl { + struct v4l2_queryctrl desc; + size_t control; +}; + +#define BLMIN 0xF3 +#define WLMAX 0x3FF + +static const struct mctl g450_controls[] = +{ { { V4L2_CID_BRIGHTNESS, V4L2_CTRL_TYPE_INTEGER, + "brightness", + 0, WLMAX-BLMIN, 1, 370-BLMIN, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.brightness) }, + { { V4L2_CID_CONTRAST, V4L2_CTRL_TYPE_INTEGER, + "contrast", + 0, 1023, 1, 127, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.contrast) }, + { { V4L2_CID_SATURATION, V4L2_CTRL_TYPE_INTEGER, + "saturation", + 0, 255, 1, 165, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.saturation) }, + { { V4L2_CID_HUE, V4L2_CTRL_TYPE_INTEGER, + "hue", + 0, 255, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.hue) }, + { { MATROXFB_CID_TESTOUT, V4L2_CTRL_TYPE_BOOLEAN, + "test output", + 0, 1, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.testout) }, +}; + +#define G450CTRLS ARRAY_SIZE(g450_controls) + +/* Return: positive number: id found + -EINVAL: id not found, return failure + -ENOENT: id not found, create fake disabled control */ +static int get_ctrl_id(__u32 v4l2_id) { + int i; + + for (i = 0; i < G450CTRLS; i++) { + if (v4l2_id < g450_controls[i].desc.id) { + if (g450_controls[i].desc.id == 0x08000000) { + return -EINVAL; + } + return -ENOENT; + } + if (v4l2_id == g450_controls[i].desc.id) { + return i; + } + } + return -EINVAL; +} + +static inline int *get_ctrl_ptr(struct matrox_fb_info *minfo, unsigned int idx) +{ + return (int*)((char*)minfo + g450_controls[idx].control); +} + +static void tvo_fill_defaults(struct matrox_fb_info *minfo) +{ + unsigned int i; + + for (i = 0; i < G450CTRLS; i++) { + *get_ctrl_ptr(minfo, i) = g450_controls[i].desc.default_value; + } +} + +static int cve2_get_reg(struct matrox_fb_info *minfo, int reg) +{ + unsigned long flags; + int val; + + matroxfb_DAC_lock_irqsave(flags); + matroxfb_DAC_out(minfo, 0x87, reg); + val = matroxfb_DAC_in(minfo, 0x88); + matroxfb_DAC_unlock_irqrestore(flags); + return val; +} + +static void cve2_set_reg(struct matrox_fb_info *minfo, int reg, int val) +{ + unsigned long flags; + + matroxfb_DAC_lock_irqsave(flags); + matroxfb_DAC_out(minfo, 0x87, reg); + matroxfb_DAC_out(minfo, 0x88, val); + matroxfb_DAC_unlock_irqrestore(flags); +} + +static void cve2_set_reg10(struct matrox_fb_info *minfo, int reg, int val) +{ + unsigned long flags; + + matroxfb_DAC_lock_irqsave(flags); + matroxfb_DAC_out(minfo, 0x87, reg); + matroxfb_DAC_out(minfo, 0x88, val >> 2); + matroxfb_DAC_out(minfo, 0x87, reg + 1); + matroxfb_DAC_out(minfo, 0x88, val & 3); + matroxfb_DAC_unlock_irqrestore(flags); +} + +static void g450_compute_bwlevel(const struct matrox_fb_info *minfo, int *bl, + int *wl) +{ + const int b = minfo->altout.tvo_params.brightness + BLMIN; + const int c = minfo->altout.tvo_params.contrast; + + *bl = max(b - c, BLMIN); + *wl = min(b + c, WLMAX); +} + +static int g450_query_ctrl(void* md, struct v4l2_queryctrl *p) { + int i; + + i = get_ctrl_id(p->id); + if (i >= 0) { + *p = g450_controls[i].desc; + return 0; + } + if (i == -ENOENT) { + static const struct v4l2_queryctrl disctrl = + { .flags = V4L2_CTRL_FLAG_DISABLED }; + + i = p->id; + *p = disctrl; + p->id = i; + sprintf(p->name, "Ctrl #%08X", i); + return 0; + } + return -EINVAL; +} + +static int g450_set_ctrl(void* md, struct v4l2_control *p) { + int i; + struct matrox_fb_info *minfo = md; + + i = get_ctrl_id(p->id); + if (i < 0) return -EINVAL; + + /* + * Check if changed. + */ + if (p->value == *get_ctrl_ptr(minfo, i)) return 0; + + /* + * Check limits. + */ + if (p->value > g450_controls[i].desc.maximum) return -EINVAL; + if (p->value < g450_controls[i].desc.minimum) return -EINVAL; + + /* + * Store new value. + */ + *get_ctrl_ptr(minfo, i) = p->value; + + switch (p->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + { + int blacklevel, whitelevel; + g450_compute_bwlevel(minfo, &blacklevel, &whitelevel); + cve2_set_reg10(minfo, 0x0e, blacklevel); + cve2_set_reg10(minfo, 0x1e, whitelevel); + } + break; + case V4L2_CID_SATURATION: + cve2_set_reg(minfo, 0x20, p->value); + cve2_set_reg(minfo, 0x22, p->value); + break; + case V4L2_CID_HUE: + cve2_set_reg(minfo, 0x25, p->value); + break; + case MATROXFB_CID_TESTOUT: + { + unsigned char val = cve2_get_reg(minfo, 0x05); + if (p->value) val |= 0x02; + else val &= ~0x02; + cve2_set_reg(minfo, 0x05, val); + } + break; + } + + + return 0; +} + +static int g450_get_ctrl(void* md, struct v4l2_control *p) { + int i; + struct matrox_fb_info *minfo = md; + + i = get_ctrl_id(p->id); + if (i < 0) return -EINVAL; + p->value = *get_ctrl_ptr(minfo, i); + return 0; +} + +struct output_desc { + unsigned int h_vis; + unsigned int h_f_porch; + unsigned int h_sync; + unsigned int h_b_porch; + unsigned long long int chromasc; + unsigned int burst; + unsigned int v_total; +}; + +static void computeRegs(struct matrox_fb_info *minfo, struct mavenregs *r, + struct my_timming *mt, const struct output_desc *outd) +{ + u_int32_t chromasc; + u_int32_t hlen; + u_int32_t hsl; + u_int32_t hbp; + u_int32_t hfp; + u_int32_t hvis; + unsigned int pixclock; + unsigned long long piic; + int mnp; + int over; + + r->regs[0x80] = 0x03; /* | 0x40 for SCART */ + + hvis = ((mt->HDisplay << 1) + 3) & ~3; + + if (hvis >= 2048) { + hvis = 2044; + } + + piic = 1000000000ULL * hvis; + do_div(piic, outd->h_vis); + + dprintk(KERN_DEBUG "Want %u kHz pixclock\n", (unsigned int)piic); + + mnp = matroxfb_g450_setclk(minfo, piic, M_VIDEO_PLL); + + mt->mnp = mnp; + mt->pixclock = g450_mnp2f(minfo, mnp); + + dprintk(KERN_DEBUG "MNP=%08X\n", mnp); + + pixclock = 1000000000U / mt->pixclock; + + dprintk(KERN_DEBUG "Got %u ps pixclock\n", pixclock); + + piic = outd->chromasc; + do_div(piic, mt->pixclock); + chromasc = piic; + + dprintk(KERN_DEBUG "Chroma is %08X\n", chromasc); + + r->regs[0] = piic >> 24; + r->regs[1] = piic >> 16; + r->regs[2] = piic >> 8; + r->regs[3] = piic >> 0; + hbp = (((outd->h_b_porch + pixclock) / pixclock)) & ~1; + hfp = (((outd->h_f_porch + pixclock) / pixclock)) & ~1; + hsl = (((outd->h_sync + pixclock) / pixclock)) & ~1; + hlen = hvis + hfp + hsl + hbp; + over = hlen & 0x0F; + + dprintk(KERN_DEBUG "WL: vis=%u, hf=%u, hs=%u, hb=%u, total=%u\n", hvis, hfp, hsl, hbp, hlen); + + if (over) { + hfp -= over; + hlen -= over; + if (over <= 2) { + } else if (over < 10) { + hfp += 4; + hlen += 4; + } else { + hfp += 16; + hlen += 16; + } + } + + /* maybe cve2 has requirement 800 < hlen < 1184 */ + r->regs[0x08] = hsl; + r->regs[0x09] = (outd->burst + pixclock - 1) / pixclock; /* burst length */ + r->regs[0x0A] = hbp; + r->regs[0x2C] = hfp; + r->regs[0x31] = hvis / 8; + r->regs[0x32] = hvis & 7; + + dprintk(KERN_DEBUG "PG: vis=%04X, hf=%02X, hs=%02X, hb=%02X, total=%04X\n", hvis, hfp, hsl, hbp, hlen); + + r->regs[0x84] = 1; /* x sync point */ + r->regs[0x85] = 0; + hvis = hvis >> 1; + hlen = hlen >> 1; + + dprintk(KERN_DEBUG "hlen=%u hvis=%u\n", hlen, hvis); + + mt->interlaced = 1; + + mt->HDisplay = hvis & ~7; + mt->HSyncStart = mt->HDisplay + 8; + mt->HSyncEnd = (hlen & ~7) - 8; + mt->HTotal = hlen; + + { + int upper; + unsigned int vtotal; + unsigned int vsyncend; + unsigned int vdisplay; + + vtotal = mt->VTotal; + vsyncend = mt->VSyncEnd; + vdisplay = mt->VDisplay; + if (vtotal < outd->v_total) { + unsigned int yovr = outd->v_total - vtotal; + + vsyncend += yovr >> 1; + } else if (vtotal > outd->v_total) { + vdisplay = outd->v_total - 4; + vsyncend = outd->v_total; + } + upper = (outd->v_total - vsyncend) >> 1; /* in field lines */ + r->regs[0x17] = outd->v_total / 4; + r->regs[0x18] = outd->v_total & 3; + r->regs[0x33] = upper - 1; /* upper blanking */ + r->regs[0x82] = upper; /* y sync point */ + r->regs[0x83] = upper >> 8; + + mt->VDisplay = vdisplay; + mt->VSyncStart = outd->v_total - 2; + mt->VSyncEnd = outd->v_total; + mt->VTotal = outd->v_total; + } +} + +static void cve2_init_TVdata(int norm, struct mavenregs* data, const struct output_desc** outd) { + static const struct output_desc paloutd = { + .h_vis = 52148148, // ps + .h_f_porch = 1407407, // ps + .h_sync = 4666667, // ps + .h_b_porch = 5777778, // ps + .chromasc = 19042247534182ULL, // 4433618.750 Hz + .burst = 2518518, // ps + .v_total = 625, + }; + static const struct output_desc ntscoutd = { + .h_vis = 52888889, // ps + .h_f_porch = 1333333, // ps + .h_sync = 4666667, // ps + .h_b_porch = 4666667, // ps + .chromasc = 15374030659475ULL, // 3579545.454 Hz + .burst = 2418418, // ps + .v_total = 525, // lines + }; + + static const struct mavenregs palregs = { { + 0x2A, 0x09, 0x8A, 0xCB, /* 00: chroma subcarrier */ + 0x00, + 0x00, /* test */ + 0xF9, /* modified by code (F9 written...) */ + 0x00, /* ? not written */ + 0x7E, /* 08 */ + 0x44, /* 09 */ + 0x9C, /* 0A */ + 0x2E, /* 0B */ + 0x21, /* 0C */ + 0x00, /* ? not written */ +// 0x3F, 0x03, /* 0E-0F */ + 0x3C, 0x03, + 0x3C, 0x03, /* 10-11 */ + 0x1A, /* 12 */ + 0x2A, /* 13 */ + 0x1C, 0x3D, 0x14, /* 14-16 */ + 0x9C, 0x01, /* 17-18 */ + 0x00, /* 19 */ + 0xFE, /* 1A */ + 0x7E, /* 1B */ + 0x60, /* 1C */ + 0x05, /* 1D */ +// 0x89, 0x03, /* 1E-1F */ + 0xAD, 0x03, +// 0x72, /* 20 */ + 0xA5, + 0x07, /* 21 */ +// 0x72, /* 22 */ + 0xA5, + 0x00, /* 23 */ + 0x00, /* 24 */ + 0x00, /* 25 */ + 0x08, /* 26 */ + 0x04, /* 27 */ + 0x00, /* 28 */ + 0x1A, /* 29 */ + 0x55, 0x01, /* 2A-2B */ + 0x26, /* 2C */ + 0x07, 0x7E, /* 2D-2E */ + 0x02, 0x54, /* 2F-30 */ + 0xB0, 0x00, /* 31-32 */ + 0x14, /* 33 */ + 0x49, /* 34 */ + 0x00, /* 35 written multiple times */ + 0x00, /* 36 not written */ + 0xA3, /* 37 */ + 0xC8, /* 38 */ + 0x22, /* 39 */ + 0x02, /* 3A */ + 0x22, /* 3B */ + 0x3F, 0x03, /* 3C-3D */ + 0x00, /* 3E written multiple times */ + 0x00, /* 3F not written */ + } }; + static const struct mavenregs ntscregs = { { + 0x21, 0xF0, 0x7C, 0x1F, /* 00: chroma subcarrier */ + 0x00, + 0x00, /* test */ + 0xF9, /* modified by code (F9 written...) */ + 0x00, /* ? not written */ + 0x7E, /* 08 */ + 0x43, /* 09 */ + 0x7E, /* 0A */ + 0x3D, /* 0B */ + 0x00, /* 0C */ + 0x00, /* ? not written */ + 0x41, 0x00, /* 0E-0F */ + 0x3C, 0x00, /* 10-11 */ + 0x17, /* 12 */ + 0x21, /* 13 */ + 0x1B, 0x1B, 0x24, /* 14-16 */ + 0x83, 0x01, /* 17-18 */ + 0x00, /* 19 */ + 0x0F, /* 1A */ + 0x0F, /* 1B */ + 0x60, /* 1C */ + 0x05, /* 1D */ + //0x89, 0x02, /* 1E-1F */ + 0xC0, 0x02, /* 1E-1F */ + //0x5F, /* 20 */ + 0x9C, /* 20 */ + 0x04, /* 21 */ + //0x5F, /* 22 */ + 0x9C, /* 22 */ + 0x01, /* 23 */ + 0x02, /* 24 */ + 0x00, /* 25 */ + 0x0A, /* 26 */ + 0x05, /* 27 */ + 0x00, /* 28 */ + 0x10, /* 29 */ + 0xFF, 0x03, /* 2A-2B */ + 0x24, /* 2C */ + 0x0F, 0x78, /* 2D-2E */ + 0x00, 0x00, /* 2F-30 */ + 0xB2, 0x04, /* 31-32 */ + 0x14, /* 33 */ + 0x02, /* 34 */ + 0x00, /* 35 written multiple times */ + 0x00, /* 36 not written */ + 0xA3, /* 37 */ + 0xC8, /* 38 */ + 0x15, /* 39 */ + 0x05, /* 3A */ + 0x3B, /* 3B */ + 0x3C, 0x00, /* 3C-3D */ + 0x00, /* 3E written multiple times */ + 0x00, /* never written */ + } }; + + if (norm == MATROXFB_OUTPUT_MODE_PAL) { + *data = palregs; + *outd = &paloutd; + } else { + *data = ntscregs; + *outd = &ntscoutd; + } + return; +} + +#define LR(x) cve2_set_reg(minfo, (x), m->regs[(x)]) +static void cve2_init_TV(struct matrox_fb_info *minfo, + const struct mavenregs *m) +{ + int i; + + LR(0x80); + LR(0x82); LR(0x83); + LR(0x84); LR(0x85); + + cve2_set_reg(minfo, 0x3E, 0x01); + + for (i = 0; i < 0x3E; i++) { + LR(i); + } + cve2_set_reg(minfo, 0x3E, 0x00); +} + +static int matroxfb_g450_compute(void* md, struct my_timming* mt) { + struct matrox_fb_info *minfo = md; + + dprintk(KERN_DEBUG "Computing, mode=%u\n", minfo->outputs[1].mode); + + if (mt->crtc == MATROXFB_SRC_CRTC2 && + minfo->outputs[1].mode != MATROXFB_OUTPUT_MODE_MONITOR) { + const struct output_desc* outd; + + cve2_init_TVdata(minfo->outputs[1].mode, &minfo->hw.maven, &outd); + { + int blacklevel, whitelevel; + g450_compute_bwlevel(minfo, &blacklevel, &whitelevel); + minfo->hw.maven.regs[0x0E] = blacklevel >> 2; + minfo->hw.maven.regs[0x0F] = blacklevel & 3; + minfo->hw.maven.regs[0x1E] = whitelevel >> 2; + minfo->hw.maven.regs[0x1F] = whitelevel & 3; + + minfo->hw.maven.regs[0x20] = + minfo->hw.maven.regs[0x22] = minfo->altout.tvo_params.saturation; + + minfo->hw.maven.regs[0x25] = minfo->altout.tvo_params.hue; + + if (minfo->altout.tvo_params.testout) { + minfo->hw.maven.regs[0x05] |= 0x02; + } + } + computeRegs(minfo, &minfo->hw.maven, mt, outd); + } else if (mt->mnp < 0) { + /* We must program clocks before CRTC2, otherwise interlaced mode + startup may fail */ + mt->mnp = matroxfb_g450_setclk(minfo, mt->pixclock, (mt->crtc == MATROXFB_SRC_CRTC1) ? M_PIXEL_PLL_C : M_VIDEO_PLL); + mt->pixclock = g450_mnp2f(minfo, mt->mnp); + } + dprintk(KERN_DEBUG "Pixclock = %u\n", mt->pixclock); + return 0; +} + +static int matroxfb_g450_program(void* md) { + struct matrox_fb_info *minfo = md; + + if (minfo->outputs[1].mode != MATROXFB_OUTPUT_MODE_MONITOR) { + cve2_init_TV(minfo, &minfo->hw.maven); + } + return 0; +} + +static int matroxfb_g450_verify_mode(void* md, u_int32_t arg) { + switch (arg) { + case MATROXFB_OUTPUT_MODE_PAL: + case MATROXFB_OUTPUT_MODE_NTSC: + case MATROXFB_OUTPUT_MODE_MONITOR: + return 0; + } + return -EINVAL; +} + +static int g450_dvi_compute(void* md, struct my_timming* mt) { + struct matrox_fb_info *minfo = md; + + if (mt->mnp < 0) { + mt->mnp = matroxfb_g450_setclk(minfo, mt->pixclock, (mt->crtc == MATROXFB_SRC_CRTC1) ? M_PIXEL_PLL_C : M_VIDEO_PLL); + mt->pixclock = g450_mnp2f(minfo, mt->mnp); + } + return 0; +} + +static struct matrox_altout matroxfb_g450_altout = { + .name = "Secondary output", + .compute = matroxfb_g450_compute, + .program = matroxfb_g450_program, + .verifymode = matroxfb_g450_verify_mode, + .getqueryctrl = g450_query_ctrl, + .getctrl = g450_get_ctrl, + .setctrl = g450_set_ctrl, +}; + +static struct matrox_altout matroxfb_g450_dvi = { + .name = "DVI output", + .compute = g450_dvi_compute, +}; + +void matroxfb_g450_connect(struct matrox_fb_info *minfo) +{ + if (minfo->devflags.g450dac) { + down_write(&minfo->altout.lock); + tvo_fill_defaults(minfo); + minfo->outputs[1].src = minfo->outputs[1].default_src; + minfo->outputs[1].data = minfo; + minfo->outputs[1].output = &matroxfb_g450_altout; + minfo->outputs[1].mode = MATROXFB_OUTPUT_MODE_MONITOR; + minfo->outputs[2].src = minfo->outputs[2].default_src; + minfo->outputs[2].data = minfo; + minfo->outputs[2].output = &matroxfb_g450_dvi; + minfo->outputs[2].mode = MATROXFB_OUTPUT_MODE_MONITOR; + up_write(&minfo->altout.lock); + } +} + +void matroxfb_g450_shutdown(struct matrox_fb_info *minfo) +{ + if (minfo->devflags.g450dac) { + down_write(&minfo->altout.lock); + minfo->outputs[1].src = MATROXFB_SRC_NONE; + minfo->outputs[1].output = NULL; + minfo->outputs[1].data = NULL; + minfo->outputs[1].mode = MATROXFB_OUTPUT_MODE_MONITOR; + minfo->outputs[2].src = MATROXFB_SRC_NONE; + minfo->outputs[2].output = NULL; + minfo->outputs[2].data = NULL; + minfo->outputs[2].mode = MATROXFB_OUTPUT_MODE_MONITOR; + up_write(&minfo->altout.lock); + } +} + +EXPORT_SYMBOL(matroxfb_g450_connect); +EXPORT_SYMBOL(matroxfb_g450_shutdown); + +MODULE_AUTHOR("(c) 2000-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Matrox G450/G550 output driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_g450.h b/drivers/video/fbdev/matrox/matroxfb_g450.h new file mode 100644 index 000000000..b5f17b86e --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_g450.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MATROXFB_G450_H__ +#define __MATROXFB_G450_H__ + +#include "matroxfb_base.h" + +#ifdef CONFIG_FB_MATROX_G +void matroxfb_g450_connect(struct matrox_fb_info *minfo); +void matroxfb_g450_shutdown(struct matrox_fb_info *minfo); +#else +static inline void matroxfb_g450_connect(struct matrox_fb_info *minfo) { }; +static inline void matroxfb_g450_shutdown(struct matrox_fb_info *minfo) { }; +#endif + +#endif /* __MATROXFB_G450_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_maven.c b/drivers/video/fbdev/matrox/matroxfb_maven.c new file mode 100644 index 000000000..fe057a0b5 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_maven.c @@ -0,0 +1,1302 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * See matroxfb_base.c for contributors. + * + */ + +#include "matroxfb_maven.h" +#include "matroxfb_misc.h" +#include "matroxfb_DAC1064.h" +#include <linux/i2c.h> +#include <linux/matroxfb.h> +#include <linux/slab.h> +#include <asm/div64.h> + +#define MGATVO_B 1 +#define MGATVO_C 2 + +static const struct maven_gamma { + unsigned char reg83; + unsigned char reg84; + unsigned char reg85; + unsigned char reg86; + unsigned char reg87; + unsigned char reg88; + unsigned char reg89; + unsigned char reg8a; + unsigned char reg8b; +} maven_gamma[] = { + { 131, 57, 223, 15, 117, 212, 251, 91, 156}, + { 133, 61, 128, 63, 180, 147, 195, 100, 180}, + { 131, 19, 63, 31, 50, 66, 171, 64, 176}, + { 0, 0, 0, 31, 16, 16, 16, 100, 200}, + { 8, 23, 47, 73, 147, 244, 220, 80, 195}, + { 22, 43, 64, 80, 147, 115, 58, 85, 168}, + { 34, 60, 80, 214, 147, 212, 188, 85, 167}, + { 45, 77, 96, 216, 147, 99, 91, 85, 159}, + { 56, 76, 112, 107, 147, 212, 148, 64, 144}, + { 65, 91, 128, 137, 147, 196, 17, 69, 148}, + { 72, 104, 136, 138, 147, 180, 245, 73, 147}, + { 87, 116, 143, 126, 16, 83, 229, 77, 144}, + { 95, 119, 152, 254, 244, 83, 221, 77, 151}, + { 100, 129, 159, 156, 244, 148, 197, 77, 160}, + { 105, 141, 167, 247, 244, 132, 181, 84, 166}, + { 105, 147, 168, 247, 244, 245, 181, 90, 170}, + { 120, 153, 175, 248, 212, 229, 165, 90, 180}, + { 119, 156, 176, 248, 244, 229, 84, 74, 160}, + { 119, 158, 183, 248, 244, 229, 149, 78, 165} +}; + +/* Definition of the various controls */ +struct mctl { + struct v4l2_queryctrl desc; + size_t control; +}; + +#define BLMIN 0x0FF +#define WLMAX 0x3FF + +static const struct mctl maven_controls[] = +{ { { V4L2_CID_BRIGHTNESS, V4L2_CTRL_TYPE_INTEGER, + "brightness", + 0, WLMAX - BLMIN, 1, 379 - BLMIN, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.brightness) }, + { { V4L2_CID_CONTRAST, V4L2_CTRL_TYPE_INTEGER, + "contrast", + 0, 1023, 1, 127, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.contrast) }, + { { V4L2_CID_SATURATION, V4L2_CTRL_TYPE_INTEGER, + "saturation", + 0, 255, 1, 155, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.saturation) }, + { { V4L2_CID_HUE, V4L2_CTRL_TYPE_INTEGER, + "hue", + 0, 255, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.hue) }, + { { V4L2_CID_GAMMA, V4L2_CTRL_TYPE_INTEGER, + "gamma", + 0, ARRAY_SIZE(maven_gamma) - 1, 1, 3, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.gamma) }, + { { MATROXFB_CID_TESTOUT, V4L2_CTRL_TYPE_BOOLEAN, + "test output", + 0, 1, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.testout) }, + { { MATROXFB_CID_DEFLICKER, V4L2_CTRL_TYPE_INTEGER, + "deflicker mode", + 0, 2, 1, 0, + 0, + }, offsetof(struct matrox_fb_info, altout.tvo_params.deflicker) }, + +}; + +#define MAVCTRLS ARRAY_SIZE(maven_controls) + +/* Return: positive number: id found + -EINVAL: id not found, return failure + -ENOENT: id not found, create fake disabled control */ +static int get_ctrl_id(__u32 v4l2_id) { + int i; + + for (i = 0; i < MAVCTRLS; i++) { + if (v4l2_id < maven_controls[i].desc.id) { + if (maven_controls[i].desc.id == 0x08000000) { + return -EINVAL; + } + return -ENOENT; + } + if (v4l2_id == maven_controls[i].desc.id) { + return i; + } + } + return -EINVAL; +} + +struct maven_data { + struct matrox_fb_info* primary_head; + struct i2c_client *client; + int version; +}; + +static int* get_ctrl_ptr(struct maven_data* md, int idx) { + return (int*)((char*)(md->primary_head) + maven_controls[idx].control); +} + +static int maven_get_reg(struct i2c_client* c, char reg) { + char dst; + struct i2c_msg msgs[] = { + { + .addr = c->addr, + .flags = I2C_M_REV_DIR_ADDR, + .len = sizeof(reg), + .buf = ® + }, + { + .addr = c->addr, + .flags = I2C_M_RD | I2C_M_NOSTART, + .len = sizeof(dst), + .buf = &dst + } + }; + s32 err; + + err = i2c_transfer(c->adapter, msgs, 2); + if (err < 0) + printk(KERN_INFO "ReadReg(%d) failed\n", reg); + return dst & 0xFF; +} + +static int maven_set_reg(struct i2c_client* c, int reg, int val) { + s32 err; + + err = i2c_smbus_write_byte_data(c, reg, val); + if (err) + printk(KERN_INFO "WriteReg(%d) failed\n", reg); + return err; +} + +static int maven_set_reg_pair(struct i2c_client* c, int reg, int val) { + s32 err; + + err = i2c_smbus_write_word_data(c, reg, val); + if (err) + printk(KERN_INFO "WriteRegPair(%d) failed\n", reg); + return err; +} + +static const struct matrox_pll_features maven_pll = { + 50000, + 27000, + 4, 127, + 2, 31, + 3 +}; + +struct matrox_pll_features2 { + unsigned int vco_freq_min; + unsigned int vco_freq_max; + unsigned int feed_div_min; + unsigned int feed_div_max; + unsigned int in_div_min; + unsigned int in_div_max; + unsigned int post_shift_max; +}; + +struct matrox_pll_ctl { + unsigned int ref_freq; + unsigned int den; +}; + +static const struct matrox_pll_features2 maven1000_pll = { + .vco_freq_min = 50000000, + .vco_freq_max = 300000000, + .feed_div_min = 5, + .feed_div_max = 128, + .in_div_min = 3, + .in_div_max = 32, + .post_shift_max = 3 +}; + +static const struct matrox_pll_ctl maven_PAL = { + .ref_freq = 540000, + .den = 50 +}; + +static const struct matrox_pll_ctl maven_NTSC = { + .ref_freq = 450450, /* 27027000/60 == 27000000/59.94005994 */ + .den = 60 +}; + +static int matroxfb_PLL_mavenclock(const struct matrox_pll_features2* pll, + const struct matrox_pll_ctl* ctl, + unsigned int htotal, unsigned int vtotal, + unsigned int* in, unsigned int* feed, unsigned int* post, + unsigned int* h2) { + unsigned int besth2 = 0; + unsigned int fxtal = ctl->ref_freq; + unsigned int fmin = pll->vco_freq_min / ctl->den; + unsigned int fwant; + unsigned int p; + unsigned int scrlen; + unsigned int fmax; + + DBG(__func__) + + scrlen = htotal * (vtotal - 1); + fwant = htotal * vtotal; + fmax = pll->vco_freq_max / ctl->den; + + dprintk(KERN_DEBUG "want: %u, xtal: %u, h: %u, v: %u, fmax: %u\n", + fwant, fxtal, htotal, vtotal, fmax); + for (p = 1; p <= pll->post_shift_max; p++) { + if (fwant * 2 > fmax) + break; + fwant *= 2; + } + if (fwant > fmax) + return 0; + for (; p-- > 0; fwant >>= 1) { + unsigned int m; + + if (fwant < fmin) break; + for (m = pll->in_div_min; m <= pll->in_div_max; m++) { + unsigned int n; + unsigned int dvd; + unsigned int ln; + + n = (fwant * m) / fxtal; + if (n < pll->feed_div_min) + continue; + if (n > pll->feed_div_max) + break; + + ln = fxtal * n; + dvd = m << p; + + if (ln % dvd) + continue; + ln = ln / dvd; + + if (ln < scrlen + 2) + continue; + ln = ln - scrlen; + if (ln > htotal) + continue; + dprintk(KERN_DEBUG "Match: %u / %u / %u / %u\n", n, m, p, ln); + if (ln > besth2) { + dprintk(KERN_DEBUG "Better...\n"); + *h2 = besth2 = ln; + *post = p; + *in = m; + *feed = n; + } + } + } + + /* if h2/post/in/feed have not been assigned, return zero (error) */ + if (besth2 < 2) + return 0; + + dprintk(KERN_ERR "clk: %02X %02X %02X %d %d\n", *in, *feed, *post, fxtal, fwant); + return fxtal * (*feed) / (*in) * ctl->den; +} + +static int matroxfb_mavenclock(const struct matrox_pll_ctl *ctl, + unsigned int htotal, unsigned int vtotal, + unsigned int* in, unsigned int* feed, unsigned int* post, + unsigned int* htotal2) { + unsigned int fvco; + unsigned int p; + + fvco = matroxfb_PLL_mavenclock(&maven1000_pll, ctl, htotal, vtotal, in, feed, &p, htotal2); + if (!fvco) + return -EINVAL; + p = (1 << p) - 1; + if (fvco <= 100000000) + ; + else if (fvco <= 140000000) + p |= 0x08; + else if (fvco <= 180000000) + p |= 0x10; + else + p |= 0x18; + *post = p; + return 0; +} + +static void DAC1064_calcclock(unsigned int freq, unsigned int fmax, + unsigned int* in, unsigned int* feed, unsigned int* post) { + unsigned int fvco; + unsigned int p; + + fvco = matroxfb_PLL_calcclock(&maven_pll, freq, fmax, in, feed, &p); + p = (1 << p) - 1; + if (fvco <= 100000) + ; + else if (fvco <= 140000) + p |= 0x08; + else if (fvco <= 180000) + p |= 0x10; + else + p |= 0x18; + *post = p; + return; +} + +static unsigned char maven_compute_deflicker (const struct maven_data* md) { + unsigned char df; + + df = (md->version == MGATVO_B?0x40:0x00); + switch (md->primary_head->altout.tvo_params.deflicker) { + case 0: +/* df |= 0x00; */ + break; + case 1: + df |= 0xB1; + break; + case 2: + df |= 0xA2; + break; + } + return df; +} + +static void maven_compute_bwlevel (const struct maven_data* md, + int *bl, int *wl) { + const int b = md->primary_head->altout.tvo_params.brightness + BLMIN; + const int c = md->primary_head->altout.tvo_params.contrast; + + *bl = max(b - c, BLMIN); + *wl = min(b + c, WLMAX); +} + +static const struct maven_gamma* maven_compute_gamma (const struct maven_data* md) { + return maven_gamma + md->primary_head->altout.tvo_params.gamma; +} + + +static void maven_init_TVdata(const struct maven_data* md, struct mavenregs* data) { + static struct mavenregs palregs = { { + 0x2A, 0x09, 0x8A, 0xCB, /* 00: chroma subcarrier */ + 0x00, + 0x00, /* ? not written */ + 0x00, /* modified by code (F9 written...) */ + 0x00, /* ? not written */ + 0x7E, /* 08 */ + 0x44, /* 09 */ + 0x9C, /* 0A */ + 0x2E, /* 0B */ + 0x21, /* 0C */ + 0x00, /* ? not written */ + 0x3F, 0x03, /* 0E-0F */ + 0x3F, 0x03, /* 10-11 */ + 0x1A, /* 12 */ + 0x2A, /* 13 */ + 0x1C, 0x3D, 0x14, /* 14-16 */ + 0x9C, 0x01, /* 17-18 */ + 0x00, /* 19 */ + 0xFE, /* 1A */ + 0x7E, /* 1B */ + 0x60, /* 1C */ + 0x05, /* 1D */ + 0x89, 0x03, /* 1E-1F */ + 0x72, /* 20 */ + 0x07, /* 21 */ + 0x72, /* 22 */ + 0x00, /* 23 */ + 0x00, /* 24 */ + 0x00, /* 25 */ + 0x08, /* 26 */ + 0x04, /* 27 */ + 0x00, /* 28 */ + 0x1A, /* 29 */ + 0x55, 0x01, /* 2A-2B */ + 0x26, /* 2C */ + 0x07, 0x7E, /* 2D-2E */ + 0x02, 0x54, /* 2F-30 */ + 0xB0, 0x00, /* 31-32 */ + 0x14, /* 33 */ + 0x49, /* 34 */ + 0x00, /* 35 written multiple times */ + 0x00, /* 36 not written */ + 0xA3, /* 37 */ + 0xC8, /* 38 */ + 0x22, /* 39 */ + 0x02, /* 3A */ + 0x22, /* 3B */ + 0x3F, 0x03, /* 3C-3D */ + 0x00, /* 3E written multiple times */ + 0x00, /* 3F not written */ + }, MATROXFB_OUTPUT_MODE_PAL, 625, 50 }; + static struct mavenregs ntscregs = { { + 0x21, 0xF0, 0x7C, 0x1F, /* 00: chroma subcarrier */ + 0x00, + 0x00, /* ? not written */ + 0x00, /* modified by code (F9 written...) */ + 0x00, /* ? not written */ + 0x7E, /* 08 */ + 0x43, /* 09 */ + 0x7E, /* 0A */ + 0x3D, /* 0B */ + 0x00, /* 0C */ + 0x00, /* ? not written */ + 0x41, 0x00, /* 0E-0F */ + 0x3C, 0x00, /* 10-11 */ + 0x17, /* 12 */ + 0x21, /* 13 */ + 0x1B, 0x1B, 0x24, /* 14-16 */ + 0x83, 0x01, /* 17-18 */ + 0x00, /* 19 */ + 0x0F, /* 1A */ + 0x0F, /* 1B */ + 0x60, /* 1C */ + 0x05, /* 1D */ + 0x89, 0x02, /* 1E-1F */ + 0x5F, /* 20 */ + 0x04, /* 21 */ + 0x5F, /* 22 */ + 0x01, /* 23 */ + 0x02, /* 24 */ + 0x00, /* 25 */ + 0x0A, /* 26 */ + 0x05, /* 27 */ + 0x00, /* 28 */ + 0x10, /* 29 */ + 0xFF, 0x03, /* 2A-2B */ + 0x24, /* 2C */ + 0x0F, 0x78, /* 2D-2E */ + 0x00, 0x00, /* 2F-30 */ + 0xB2, 0x04, /* 31-32 */ + 0x14, /* 33 */ + 0x02, /* 34 */ + 0x00, /* 35 written multiple times */ + 0x00, /* 36 not written */ + 0xA3, /* 37 */ + 0xC8, /* 38 */ + 0x15, /* 39 */ + 0x05, /* 3A */ + 0x3B, /* 3B */ + 0x3C, 0x00, /* 3C-3D */ + 0x00, /* 3E written multiple times */ + 0x00, /* never written */ + }, MATROXFB_OUTPUT_MODE_NTSC, 525, 60 }; + struct matrox_fb_info *minfo = md->primary_head; + + if (minfo->outputs[1].mode == MATROXFB_OUTPUT_MODE_PAL) + *data = palregs; + else + *data = ntscregs; + + /* Set deflicker */ + data->regs[0x93] = maven_compute_deflicker(md); + + /* set gamma */ + { + const struct maven_gamma* g; + g = maven_compute_gamma(md); + data->regs[0x83] = g->reg83; + data->regs[0x84] = g->reg84; + data->regs[0x85] = g->reg85; + data->regs[0x86] = g->reg86; + data->regs[0x87] = g->reg87; + data->regs[0x88] = g->reg88; + data->regs[0x89] = g->reg89; + data->regs[0x8A] = g->reg8a; + data->regs[0x8B] = g->reg8b; + } + + /* Set contrast / brightness */ + { + int bl, wl; + maven_compute_bwlevel (md, &bl, &wl); + data->regs[0x0e] = bl >> 2; + data->regs[0x0f] = bl & 3; + data->regs[0x1e] = wl >> 2; + data->regs[0x1f] = wl & 3; + } + + /* Set saturation */ + { + data->regs[0x20] = + data->regs[0x22] = minfo->altout.tvo_params.saturation; + } + + /* Set HUE */ + data->regs[0x25] = minfo->altout.tvo_params.hue; + return; +} + +#define LR(x) maven_set_reg(c, (x), m->regs[(x)]) +#define LRP(x) maven_set_reg_pair(c, (x), m->regs[(x)] | (m->regs[(x)+1] << 8)) +static void maven_init_TV(struct i2c_client* c, const struct mavenregs* m) { + int val; + + + maven_set_reg(c, 0x3E, 0x01); + maven_get_reg(c, 0x82); /* fetch oscillator state? */ + maven_set_reg(c, 0x8C, 0x00); + maven_get_reg(c, 0x94); /* get 0x82 */ + maven_set_reg(c, 0x94, 0xA2); + /* xmiscctrl */ + + maven_set_reg_pair(c, 0x8E, 0x1EFF); + maven_set_reg(c, 0xC6, 0x01); + + /* removed code... */ + + maven_get_reg(c, 0x06); + maven_set_reg(c, 0x06, 0xF9); /* or read |= 0xF0 ? */ + + /* removed code here... */ + + /* real code begins here? */ + /* chroma subcarrier */ + LR(0x00); LR(0x01); LR(0x02); LR(0x03); + + LR(0x04); + + LR(0x2C); + LR(0x08); + LR(0x0A); + LR(0x09); + LR(0x29); + LRP(0x31); + LRP(0x17); + LR(0x0B); + LR(0x0C); + if (m->mode == MATROXFB_OUTPUT_MODE_PAL) { + maven_set_reg(c, 0x35, 0x10); /* ... */ + } else { + maven_set_reg(c, 0x35, 0x0F); /* ... */ + } + + LRP(0x10); + + LRP(0x0E); + LRP(0x1E); + + LR(0x20); /* saturation #1 */ + LR(0x22); /* saturation #2 */ + LR(0x25); /* hue */ + LR(0x34); + LR(0x33); + LR(0x19); + LR(0x12); + LR(0x3B); + LR(0x13); + LR(0x39); + LR(0x1D); + LR(0x3A); + LR(0x24); + LR(0x14); + LR(0x15); + LR(0x16); + LRP(0x2D); + LRP(0x2F); + LR(0x1A); + LR(0x1B); + LR(0x1C); + LR(0x23); + LR(0x26); + LR(0x28); + LR(0x27); + LR(0x21); + LRP(0x2A); + if (m->mode == MATROXFB_OUTPUT_MODE_PAL) + maven_set_reg(c, 0x35, 0x1D); /* ... */ + else + maven_set_reg(c, 0x35, 0x1C); + + LRP(0x3C); + LR(0x37); + LR(0x38); + maven_set_reg(c, 0xB3, 0x01); + + maven_get_reg(c, 0xB0); /* read 0x80 */ + maven_set_reg(c, 0xB0, 0x08); /* ugh... */ + maven_get_reg(c, 0xB9); /* read 0x7C */ + maven_set_reg(c, 0xB9, 0x78); + maven_get_reg(c, 0xBF); /* read 0x00 */ + maven_set_reg(c, 0xBF, 0x02); + maven_get_reg(c, 0x94); /* read 0x82 */ + maven_set_reg(c, 0x94, 0xB3); + + LR(0x80); /* 04 1A 91 or 05 21 91 */ + LR(0x81); + LR(0x82); + + maven_set_reg(c, 0x8C, 0x20); + maven_get_reg(c, 0x8D); + maven_set_reg(c, 0x8D, 0x10); + + LR(0x90); /* 4D 50 52 or 4E 05 45 */ + LR(0x91); + LR(0x92); + + LRP(0x9A); /* 0049 or 004F */ + LRP(0x9C); /* 0004 or 0004 */ + LRP(0x9E); /* 0458 or 045E */ + LRP(0xA0); /* 05DA or 051B */ + LRP(0xA2); /* 00CC or 00CF */ + LRP(0xA4); /* 007D or 007F */ + LRP(0xA6); /* 007C or 007E */ + LRP(0xA8); /* 03CB or 03CE */ + LRP(0x98); /* 0000 or 0000 */ + LRP(0xAE); /* 0044 or 003A */ + LRP(0x96); /* 05DA or 051B */ + LRP(0xAA); /* 04BC or 046A */ + LRP(0xAC); /* 004D or 004E */ + + LR(0xBE); + LR(0xC2); + + maven_get_reg(c, 0x8D); + maven_set_reg(c, 0x8D, 0x04); + + LR(0x20); /* saturation #1 */ + LR(0x22); /* saturation #2 */ + LR(0x93); /* whoops */ + LR(0x20); /* oh, saturation #1 again */ + LR(0x22); /* oh, saturation #2 again */ + LR(0x25); /* hue */ + LRP(0x0E); + LRP(0x1E); + LRP(0x0E); /* problems with memory? */ + LRP(0x1E); /* yes, matrox must have problems in memory area... */ + + /* load gamma correction stuff */ + LR(0x83); + LR(0x84); + LR(0x85); + LR(0x86); + LR(0x87); + LR(0x88); + LR(0x89); + LR(0x8A); + LR(0x8B); + + val = maven_get_reg(c, 0x8D); + val &= 0x14; /* 0x10 or anything ored with it */ + maven_set_reg(c, 0x8D, val); + + LR(0x33); + LR(0x19); + LR(0x12); + LR(0x3B); + LR(0x13); + LR(0x39); + LR(0x1D); + LR(0x3A); + LR(0x24); + LR(0x14); + LR(0x15); + LR(0x16); + LRP(0x2D); + LRP(0x2F); + LR(0x1A); + LR(0x1B); + LR(0x1C); + LR(0x23); + LR(0x26); + LR(0x28); + LR(0x27); + LR(0x21); + LRP(0x2A); + if (m->mode == MATROXFB_OUTPUT_MODE_PAL) + maven_set_reg(c, 0x35, 0x1D); + else + maven_set_reg(c, 0x35, 0x1C); + LRP(0x3C); + LR(0x37); + LR(0x38); + + maven_get_reg(c, 0xB0); + LR(0xB0); /* output mode */ + LR(0x90); + LR(0xBE); + LR(0xC2); + + LRP(0x9A); + LRP(0xA2); + LRP(0x9E); + LRP(0xA6); + LRP(0xAA); + LRP(0xAC); + maven_set_reg(c, 0x3E, 0x00); + maven_set_reg(c, 0x95, 0x20); +} + +static int maven_find_exact_clocks(unsigned int ht, unsigned int vt, + struct mavenregs* m) { + unsigned int x; + unsigned int err = ~0; + + /* 1:1 */ + m->regs[0x80] = 0x0F; + m->regs[0x81] = 0x07; + m->regs[0x82] = 0x81; + + for (x = 0; x < 8; x++) { + unsigned int c; + unsigned int a, b, + h2; + unsigned int h = ht + 2 + x; + + if (!matroxfb_mavenclock((m->mode == MATROXFB_OUTPUT_MODE_PAL) ? &maven_PAL : &maven_NTSC, h, vt, &a, &b, &c, &h2)) { + unsigned int diff = h - h2; + + if (diff < err) { + err = diff; + m->regs[0x80] = a - 1; + m->regs[0x81] = b - 1; + m->regs[0x82] = c | 0x80; + m->hcorr = h2 - 2; + m->htotal = h - 2; + } + } + } + return err != ~0U; +} + +static inline int maven_compute_timming(struct maven_data* md, + struct my_timming* mt, + struct mavenregs* m) { + unsigned int tmpi; + unsigned int a, bv, c; + struct matrox_fb_info *minfo = md->primary_head; + + m->mode = minfo->outputs[1].mode; + if (m->mode != MATROXFB_OUTPUT_MODE_MONITOR) { + unsigned int lmargin; + unsigned int umargin; + unsigned int vslen; + unsigned int hcrt; + unsigned int slen; + + maven_init_TVdata(md, m); + + if (maven_find_exact_clocks(mt->HTotal, mt->VTotal, m) == 0) + return -EINVAL; + + lmargin = mt->HTotal - mt->HSyncEnd; + slen = mt->HSyncEnd - mt->HSyncStart; + hcrt = mt->HTotal - slen - mt->delay; + umargin = mt->VTotal - mt->VSyncEnd; + vslen = mt->VSyncEnd - mt->VSyncStart; + + if (m->hcorr < mt->HTotal) + hcrt += m->hcorr; + if (hcrt > mt->HTotal) + hcrt -= mt->HTotal; + if (hcrt + 2 > mt->HTotal) + hcrt = 0; /* or issue warning? */ + + /* last (first? middle?) line in picture can have different length */ + /* hlen - 2 */ + m->regs[0x96] = m->hcorr; + m->regs[0x97] = m->hcorr >> 8; + /* ... */ + m->regs[0x98] = 0x00; m->regs[0x99] = 0x00; + /* hblanking end */ + m->regs[0x9A] = lmargin; /* 100% */ + m->regs[0x9B] = lmargin >> 8; /* 100% */ + /* who knows */ + m->regs[0x9C] = 0x04; + m->regs[0x9D] = 0x00; + /* htotal - 2 */ + m->regs[0xA0] = m->htotal; + m->regs[0xA1] = m->htotal >> 8; + /* vblanking end */ + m->regs[0xA2] = mt->VTotal - mt->VSyncStart - 1; /* stop vblanking */ + m->regs[0xA3] = (mt->VTotal - mt->VSyncStart - 1) >> 8; + /* something end... [A6]+1..[A8] */ + if (md->version == MGATVO_B) { + m->regs[0xA4] = 0x04; + m->regs[0xA5] = 0x00; + } else { + m->regs[0xA4] = 0x01; + m->regs[0xA5] = 0x00; + } + /* something start... 0..[A4]-1 */ + m->regs[0xA6] = 0x00; + m->regs[0xA7] = 0x00; + /* vertical line count - 1 */ + m->regs[0xA8] = mt->VTotal - 1; + m->regs[0xA9] = (mt->VTotal - 1) >> 8; + /* horizontal vidrst pos */ + m->regs[0xAA] = hcrt; /* 0 <= hcrt <= htotal - 2 */ + m->regs[0xAB] = hcrt >> 8; + /* vertical vidrst pos */ + m->regs[0xAC] = mt->VTotal - 2; + m->regs[0xAD] = (mt->VTotal - 2) >> 8; + /* moves picture up/down and so on... */ + m->regs[0xAE] = 0x01; /* Fix this... 0..VTotal */ + m->regs[0xAF] = 0x00; + { + int hdec; + int hlen; + unsigned int ibmin = 4 + lmargin + mt->HDisplay; + unsigned int ib; + int i; + + /* Verify! */ + /* Where 94208 came from? */ + if (mt->HTotal) + hdec = 94208 / (mt->HTotal); + else + hdec = 0x81; + if (hdec > 0x81) + hdec = 0x81; + if (hdec < 0x41) + hdec = 0x41; + hdec--; + hlen = 98304 - 128 - ((lmargin + mt->HDisplay - 8) * hdec); + if (hlen < 0) + hlen = 0; + hlen = hlen >> 8; + if (hlen > 0xFF) + hlen = 0xFF; + /* Now we have to compute input buffer length. + If you want any picture, it must be between + 4 + lmargin + xres + and + 94208 / hdec + If you want perfect picture even on the top + of screen, it must be also + 0x3C0000 * i / hdec + Q - R / hdec + where + R Qmin Qmax + 0x07000 0x5AE 0x5BF + 0x08000 0x5CF 0x5FF + 0x0C000 0x653 0x67F + 0x10000 0x6F8 0x6FF + */ + i = 1; + do { + ib = ((0x3C0000 * i - 0x8000)/ hdec + 0x05E7) >> 8; + i++; + } while (ib < ibmin); + if (ib >= m->htotal + 2) { + ib = ibmin; + } + + m->regs[0x90] = hdec; /* < 0x40 || > 0x80 is bad... 0x80 is questionable */ + m->regs[0xC2] = hlen; + /* 'valid' input line length */ + m->regs[0x9E] = ib; + m->regs[0x9F] = ib >> 8; + } + { + int vdec; + int vlen; + +#define MATROX_USE64BIT_DIVIDE + if (mt->VTotal) { +#ifdef MATROX_USE64BIT_DIVIDE + u64 f1; + u32 a; + u32 b; + + a = m->vlines * (m->htotal + 2); + b = (mt->VTotal - 1) * (m->htotal + 2) + m->hcorr + 2; + + f1 = ((u64)a) << 15; /* *32768 */ + do_div(f1, b); + vdec = f1; +#else + vdec = m->vlines * 32768 / mt->VTotal; +#endif + } else + vdec = 0x8000; + if (vdec > 0x8000) + vdec = 0x8000; + vlen = (vslen + umargin + mt->VDisplay) * vdec; + vlen = (vlen >> 16) - 146; /* FIXME: 146?! */ + if (vlen < 0) + vlen = 0; + if (vlen > 0xFF) + vlen = 0xFF; + vdec--; + m->regs[0x91] = vdec; + m->regs[0x92] = vdec >> 8; + m->regs[0xBE] = vlen; + } + m->regs[0xB0] = 0x08; /* output: SVideo/Composite */ + return 0; + } + + DAC1064_calcclock(mt->pixclock, 450000, &a, &bv, &c); + m->regs[0x80] = a; + m->regs[0x81] = bv; + m->regs[0x82] = c | 0x80; + + m->regs[0xB3] = 0x01; + m->regs[0x94] = 0xB2; + + /* htotal... */ + m->regs[0x96] = mt->HTotal; + m->regs[0x97] = mt->HTotal >> 8; + /* ?? */ + m->regs[0x98] = 0x00; + m->regs[0x99] = 0x00; + /* hsync len */ + tmpi = mt->HSyncEnd - mt->HSyncStart; + m->regs[0x9A] = tmpi; + m->regs[0x9B] = tmpi >> 8; + /* hblank end */ + tmpi = mt->HTotal - mt->HSyncStart; + m->regs[0x9C] = tmpi; + m->regs[0x9D] = tmpi >> 8; + /* hblank start */ + tmpi += mt->HDisplay; + m->regs[0x9E] = tmpi; + m->regs[0x9F] = tmpi >> 8; + /* htotal + 1 */ + tmpi = mt->HTotal + 1; + m->regs[0xA0] = tmpi; + m->regs[0xA1] = tmpi >> 8; + /* vsync?! */ + tmpi = mt->VSyncEnd - mt->VSyncStart - 1; + m->regs[0xA2] = tmpi; + m->regs[0xA3] = tmpi >> 8; + /* ignored? */ + tmpi = mt->VTotal - mt->VSyncStart; + m->regs[0xA4] = tmpi; + m->regs[0xA5] = tmpi >> 8; + /* ignored? */ + tmpi = mt->VTotal - 1; + m->regs[0xA6] = tmpi; + m->regs[0xA7] = tmpi >> 8; + /* vtotal - 1 */ + m->regs[0xA8] = tmpi; + m->regs[0xA9] = tmpi >> 8; + /* hor vidrst */ + tmpi = mt->HTotal - mt->delay; + m->regs[0xAA] = tmpi; + m->regs[0xAB] = tmpi >> 8; + /* vert vidrst */ + tmpi = mt->VTotal - 2; + m->regs[0xAC] = tmpi; + m->regs[0xAD] = tmpi >> 8; + /* ignored? */ + m->regs[0xAE] = 0x00; + m->regs[0xAF] = 0x00; + + m->regs[0xB0] = 0x03; /* output: monitor */ + m->regs[0xB1] = 0xA0; /* ??? */ + m->regs[0x8C] = 0x20; /* must be set... */ + m->regs[0x8D] = 0x04; /* defaults to 0x10: test signal */ + m->regs[0xB9] = 0x1A; /* defaults to 0x2C: too bright */ + m->regs[0xBF] = 0x22; /* makes picture stable */ + + return 0; +} + +static int maven_program_timming(struct maven_data* md, + const struct mavenregs* m) { + struct i2c_client *c = md->client; + + if (m->mode == MATROXFB_OUTPUT_MODE_MONITOR) { + LR(0x80); + LR(0x81); + LR(0x82); + + LR(0xB3); + LR(0x94); + + LRP(0x96); + LRP(0x98); + LRP(0x9A); + LRP(0x9C); + LRP(0x9E); + LRP(0xA0); + LRP(0xA2); + LRP(0xA4); + LRP(0xA6); + LRP(0xA8); + LRP(0xAA); + LRP(0xAC); + LRP(0xAE); + + LR(0xB0); /* output: monitor */ + LR(0xB1); /* ??? */ + LR(0x8C); /* must be set... */ + LR(0x8D); /* defaults to 0x10: test signal */ + LR(0xB9); /* defaults to 0x2C: too bright */ + LR(0xBF); /* makes picture stable */ + } else { + maven_init_TV(c, m); + } + return 0; +} + +static inline int maven_resync(struct maven_data* md) { + struct i2c_client *c = md->client; + maven_set_reg(c, 0x95, 0x20); /* start whole thing */ + return 0; +} + +static int maven_get_queryctrl (struct maven_data* md, + struct v4l2_queryctrl *p) { + int i; + + i = get_ctrl_id(p->id); + if (i >= 0) { + *p = maven_controls[i].desc; + return 0; + } + if (i == -ENOENT) { + static const struct v4l2_queryctrl disctrl = + { .flags = V4L2_CTRL_FLAG_DISABLED }; + + i = p->id; + *p = disctrl; + p->id = i; + sprintf(p->name, "Ctrl #%08X", i); + return 0; + } + return -EINVAL; +} + +static int maven_set_control (struct maven_data* md, + struct v4l2_control *p) { + int i; + + i = get_ctrl_id(p->id); + if (i < 0) return -EINVAL; + + /* + * Check if changed. + */ + if (p->value == *get_ctrl_ptr(md, i)) return 0; + + /* + * Check limits. + */ + if (p->value > maven_controls[i].desc.maximum) return -EINVAL; + if (p->value < maven_controls[i].desc.minimum) return -EINVAL; + + /* + * Store new value. + */ + *get_ctrl_ptr(md, i) = p->value; + + switch (p->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + { + int blacklevel, whitelevel; + maven_compute_bwlevel(md, &blacklevel, &whitelevel); + blacklevel = (blacklevel >> 2) | ((blacklevel & 3) << 8); + whitelevel = (whitelevel >> 2) | ((whitelevel & 3) << 8); + maven_set_reg_pair(md->client, 0x0e, blacklevel); + maven_set_reg_pair(md->client, 0x1e, whitelevel); + } + break; + case V4L2_CID_SATURATION: + { + maven_set_reg(md->client, 0x20, p->value); + maven_set_reg(md->client, 0x22, p->value); + } + break; + case V4L2_CID_HUE: + { + maven_set_reg(md->client, 0x25, p->value); + } + break; + case V4L2_CID_GAMMA: + { + const struct maven_gamma* g; + g = maven_compute_gamma(md); + maven_set_reg(md->client, 0x83, g->reg83); + maven_set_reg(md->client, 0x84, g->reg84); + maven_set_reg(md->client, 0x85, g->reg85); + maven_set_reg(md->client, 0x86, g->reg86); + maven_set_reg(md->client, 0x87, g->reg87); + maven_set_reg(md->client, 0x88, g->reg88); + maven_set_reg(md->client, 0x89, g->reg89); + maven_set_reg(md->client, 0x8a, g->reg8a); + maven_set_reg(md->client, 0x8b, g->reg8b); + } + break; + case MATROXFB_CID_TESTOUT: + { + unsigned char val + = maven_get_reg(md->client, 0x8d); + if (p->value) val |= 0x10; + else val &= ~0x10; + maven_set_reg(md->client, 0x8d, val); + } + break; + case MATROXFB_CID_DEFLICKER: + { + maven_set_reg(md->client, 0x93, maven_compute_deflicker(md)); + } + break; + } + + + return 0; +} + +static int maven_get_control (struct maven_data* md, + struct v4l2_control *p) { + int i; + + i = get_ctrl_id(p->id); + if (i < 0) return -EINVAL; + p->value = *get_ctrl_ptr(md, i); + return 0; +} + +/******************************************************/ + +static int maven_out_compute(void* md, struct my_timming* mt) { +#define mdinfo ((struct maven_data*)md) +#define minfo (mdinfo->primary_head) + return maven_compute_timming(md, mt, &minfo->hw.maven); +#undef minfo +#undef mdinfo +} + +static int maven_out_program(void* md) { +#define mdinfo ((struct maven_data*)md) +#define minfo (mdinfo->primary_head) + return maven_program_timming(md, &minfo->hw.maven); +#undef minfo +#undef mdinfo +} + +static int maven_out_start(void* md) { + return maven_resync(md); +} + +static int maven_out_verify_mode(void* md, u_int32_t arg) { + switch (arg) { + case MATROXFB_OUTPUT_MODE_PAL: + case MATROXFB_OUTPUT_MODE_NTSC: + case MATROXFB_OUTPUT_MODE_MONITOR: + return 0; + } + return -EINVAL; +} + +static int maven_out_get_queryctrl(void* md, struct v4l2_queryctrl* p) { + return maven_get_queryctrl(md, p); +} + +static int maven_out_get_ctrl(void* md, struct v4l2_control* p) { + return maven_get_control(md, p); +} + +static int maven_out_set_ctrl(void* md, struct v4l2_control* p) { + return maven_set_control(md, p); +} + +static struct matrox_altout maven_altout = { + .name = "Secondary output", + .compute = maven_out_compute, + .program = maven_out_program, + .start = maven_out_start, + .verifymode = maven_out_verify_mode, + .getqueryctrl = maven_out_get_queryctrl, + .getctrl = maven_out_get_ctrl, + .setctrl = maven_out_set_ctrl, +}; + +static int maven_init_client(struct i2c_client* clnt) { + struct maven_data* md = i2c_get_clientdata(clnt); + struct matrox_fb_info *minfo = container_of(clnt->adapter, + struct i2c_bit_adapter, + adapter)->minfo; + + md->primary_head = minfo; + md->client = clnt; + down_write(&minfo->altout.lock); + minfo->outputs[1].output = &maven_altout; + minfo->outputs[1].src = minfo->outputs[1].default_src; + minfo->outputs[1].data = md; + minfo->outputs[1].mode = MATROXFB_OUTPUT_MODE_MONITOR; + up_write(&minfo->altout.lock); + if (maven_get_reg(clnt, 0xB2) < 0x14) { + md->version = MGATVO_B; + /* Tweak some things for this old chip */ + } else { + md->version = MGATVO_C; + } + /* + * Set all parameters to its initial values. + */ + { + unsigned int i; + + for (i = 0; i < MAVCTRLS; ++i) { + *get_ctrl_ptr(md, i) = maven_controls[i].desc.default_value; + } + } + + return 0; +} + +static int maven_shutdown_client(struct i2c_client* clnt) { + struct maven_data* md = i2c_get_clientdata(clnt); + + if (md->primary_head) { + struct matrox_fb_info *minfo = md->primary_head; + + down_write(&minfo->altout.lock); + minfo->outputs[1].src = MATROXFB_SRC_NONE; + minfo->outputs[1].output = NULL; + minfo->outputs[1].data = NULL; + minfo->outputs[1].mode = MATROXFB_OUTPUT_MODE_MONITOR; + up_write(&minfo->altout.lock); + md->primary_head = NULL; + } + return 0; +} + +static int maven_probe(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int err = -ENODEV; + struct maven_data* data; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_WORD_DATA | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_NOSTART | + I2C_FUNC_PROTOCOL_MANGLING)) + goto ERROR0; + if (!(data = kzalloc_obj(*data))) { + err = -ENOMEM; + goto ERROR0; + } + i2c_set_clientdata(client, data); + err = maven_init_client(client); + if (err) + goto ERROR4; + return 0; +ERROR4:; + kfree(data); +ERROR0:; + return err; +} + +static void maven_remove(struct i2c_client *client) +{ + maven_shutdown_client(client); + kfree(i2c_get_clientdata(client)); +} + +static const struct i2c_device_id maven_id[] = { + { .name = "maven" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, maven_id); + +static struct i2c_driver maven_driver={ + .driver = { + .name = "maven", + }, + .probe = maven_probe, + .remove = maven_remove, + .id_table = maven_id, +}; + +module_i2c_driver(maven_driver); +MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Matrox G200/G400 Matrox MGA-TVO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_maven.h b/drivers/video/fbdev/matrox/matroxfb_maven.h new file mode 100644 index 000000000..f896b7883 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_maven.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MATROXFB_MAVEN_H__ +#define __MATROXFB_MAVEN_H__ + +#include <linux/ioctl.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include "matroxfb_base.h" + +struct i2c_bit_adapter { + struct i2c_adapter adapter; + int initialized; + struct i2c_algo_bit_data bac; + struct matrox_fb_info* minfo; + struct { + unsigned int data; + unsigned int clock; + } mask; +}; + +#endif /* __MATROXFB_MAVEN_H__ */ diff --git a/drivers/video/fbdev/matrox/matroxfb_misc.c b/drivers/video/fbdev/matrox/matroxfb_misc.c new file mode 100644 index 000000000..2c5f00995 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_misc.c @@ -0,0 +1,820 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200 and G400 + * + * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> + * + * Portions Copyright (c) 2001 Matrox Graphics Inc. + * + * Version: 1.65 2002/08/14 + * + * MTRR stuff: 1998 Tom Rini <trini@kernel.crashing.org> + * + * Contributors: "menion?" <menion@mindless.com> + * Betatesting, fixes, ideas + * + * "Kurt Garloff" <garloff@suse.de> + * Betatesting, fixes, ideas, videomodes, videomodes timmings + * + * "Tom Rini" <trini@kernel.crashing.org> + * MTRR stuff, PPC cleanups, betatesting, fixes, ideas + * + * "Bibek Sahu" <scorpio@dodds.net> + * Access device through readb|w|l and write b|w|l + * Extensive debugging stuff + * + * "Daniel Haun" <haund@usa.net> + * Testing, hardware cursor fixes + * + * "Scott Wood" <sawst46+@pitt.edu> + * Fixes + * + * "Gerd Knorr" <kraxel@goldbach.isdn.cs.tu-berlin.de> + * Betatesting + * + * "Kelly French" <targon@hazmat.com> + * "Fernando Herrera" <fherrera@eurielec.etsit.upm.es> + * Betatesting, bug reporting + * + * "Pablo Bianucci" <pbian@pccp.com.ar> + * Fixes, ideas, betatesting + * + * "Inaky Perez Gonzalez" <inaky@peloncho.fis.ucm.es> + * Fixes, enhandcements, ideas, betatesting + * + * "Ryuichi Oikawa" <roikawa@rr.iiij4u.or.jp> + * PPC betatesting, PPC support, backward compatibility + * + * "Paul Womar" <Paul@pwomar.demon.co.uk> + * "Owen Waller" <O.Waller@ee.qub.ac.uk> + * PPC betatesting + * + * "Thomas Pornin" <pornin@bolet.ens.fr> + * Alpha betatesting + * + * "Pieter van Leuven" <pvl@iae.nl> + * "Ulf Jaenicke-Roessler" <ujr@physik.phy.tu-dresden.de> + * G100 testing + * + * "H. Peter Arvin" <hpa@transmeta.com> + * Ideas + * + * "Cort Dougan" <cort@cs.nmt.edu> + * CHRP fixes and PReP cleanup + * + * "Mark Vojkovich" <mvojkovi@ucsd.edu> + * G400 support + * + * "David C. Hansen" <haveblue@us.ibm.com> + * Fixes + * + * "Ian Romanick" <idr@us.ibm.com> + * Find PInS data in BIOS on PowerPC systems. + * + * (following author is not in any relation with this code, but his code + * is included in this driver) + * + * Based on framebuffer driver for VBE 2.0 compliant graphic boards + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + * (following author is not in any relation with this code, but his ideas + * were used when writing this driver) + * + * FreeVBE/AF (Matrox), "Shawn Hargreaves" <shawn@talula.demon.co.uk> + * + */ + +#include <linux/export.h> + +#include "matroxfb_misc.h" +#include <linux/interrupt.h> +#include <linux/matroxfb.h> + +void matroxfb_DAC_out(const struct matrox_fb_info *minfo, int reg, int val) +{ + DBG_REG(__func__) + mga_outb(M_RAMDAC_BASE+M_X_INDEX, reg); + mga_outb(M_RAMDAC_BASE+M_X_DATAREG, val); +} + +int matroxfb_DAC_in(const struct matrox_fb_info *minfo, int reg) +{ + DBG_REG(__func__) + mga_outb(M_RAMDAC_BASE+M_X_INDEX, reg); + return mga_inb(M_RAMDAC_BASE+M_X_DATAREG); +} + +void matroxfb_var2my(struct fb_var_screeninfo* var, struct my_timming* mt) { + unsigned int pixclock = var->pixclock; + + DBG(__func__) + + if (!pixclock) pixclock = 10000; /* 10ns = 100MHz */ + mt->pixclock = 1000000000 / pixclock; + if (mt->pixclock < 1) mt->pixclock = 1; + mt->mnp = -1; + mt->dblscan = var->vmode & FB_VMODE_DOUBLE; + mt->interlaced = var->vmode & FB_VMODE_INTERLACED; + mt->HDisplay = var->xres; + mt->HSyncStart = mt->HDisplay + var->right_margin; + mt->HSyncEnd = mt->HSyncStart + var->hsync_len; + mt->HTotal = mt->HSyncEnd + var->left_margin; + mt->VDisplay = var->yres; + mt->VSyncStart = mt->VDisplay + var->lower_margin; + mt->VSyncEnd = mt->VSyncStart + var->vsync_len; + mt->VTotal = mt->VSyncEnd + var->upper_margin; + mt->sync = var->sync; +} + +int matroxfb_PLL_calcclock(const struct matrox_pll_features* pll, unsigned int freq, unsigned int fmax, + unsigned int* in, unsigned int* feed, unsigned int* post) { + unsigned int bestdiff = ~0; + unsigned int bestvco = 0; + unsigned int fxtal = pll->ref_freq; + unsigned int fwant; + unsigned int p; + + DBG(__func__) + + fwant = freq; + +#ifdef DEBUG + printk(KERN_ERR "post_shift_max: %d\n", pll->post_shift_max); + printk(KERN_ERR "ref_freq: %d\n", pll->ref_freq); + printk(KERN_ERR "freq: %d\n", freq); + printk(KERN_ERR "vco_freq_min: %d\n", pll->vco_freq_min); + printk(KERN_ERR "in_div_min: %d\n", pll->in_div_min); + printk(KERN_ERR "in_div_max: %d\n", pll->in_div_max); + printk(KERN_ERR "feed_div_min: %d\n", pll->feed_div_min); + printk(KERN_ERR "feed_div_max: %d\n", pll->feed_div_max); + printk(KERN_ERR "fmax: %d\n", fmax); +#endif + for (p = 1; p <= pll->post_shift_max; p++) { + if (fwant * 2 > fmax) + break; + fwant *= 2; + } + if (fwant < pll->vco_freq_min) fwant = pll->vco_freq_min; + if (fwant > fmax) fwant = fmax; + for (; p-- > 0; fwant >>= 1, bestdiff >>= 1) { + unsigned int m; + + if (fwant < pll->vco_freq_min) break; + for (m = pll->in_div_min; m <= pll->in_div_max; m++) { + unsigned int diff, fvco; + unsigned int n; + + n = (fwant * (m + 1) + (fxtal >> 1)) / fxtal - 1; + if (n > pll->feed_div_max) + break; + if (n < pll->feed_div_min) + n = pll->feed_div_min; + fvco = (fxtal * (n + 1)) / (m + 1); + if (fvco < fwant) + diff = fwant - fvco; + else + diff = fvco - fwant; + if (diff < bestdiff) { + bestdiff = diff; + *post = p; + *in = m; + *feed = n; + bestvco = fvco; + } + } + } + dprintk(KERN_ERR "clk: %02X %02X %02X %d %d %d\n", *in, *feed, *post, fxtal, bestvco, fwant); + return bestvco; +} + +int matroxfb_vgaHWinit(struct matrox_fb_info *minfo, struct my_timming *m) +{ + unsigned int hd, hs, he, hbe, ht; + unsigned int vd, vs, ve, vt, lc; + unsigned int wd; + unsigned int divider; + int i; + struct matrox_hw_state * const hw = &minfo->hw; + + DBG(__func__) + + hw->SEQ[0] = 0x00; + hw->SEQ[1] = 0x01; /* or 0x09 */ + hw->SEQ[2] = 0x0F; /* bitplanes */ + hw->SEQ[3] = 0x00; + hw->SEQ[4] = 0x0E; + /* CRTC 0..7, 9, 16..19, 21, 22 are reprogrammed by Matrox Millennium code... Hope that by MGA1064 too */ + if (m->dblscan) { + m->VTotal <<= 1; + m->VDisplay <<= 1; + m->VSyncStart <<= 1; + m->VSyncEnd <<= 1; + } + if (m->interlaced) { + m->VTotal >>= 1; + m->VDisplay >>= 1; + m->VSyncStart >>= 1; + m->VSyncEnd >>= 1; + } + + /* GCTL is ignored when not using 0xA0000 aperture */ + hw->GCTL[0] = 0x00; + hw->GCTL[1] = 0x00; + hw->GCTL[2] = 0x00; + hw->GCTL[3] = 0x00; + hw->GCTL[4] = 0x00; + hw->GCTL[5] = 0x40; + hw->GCTL[6] = 0x05; + hw->GCTL[7] = 0x0F; + hw->GCTL[8] = 0xFF; + + /* Whole ATTR is ignored in PowerGraphics mode */ + for (i = 0; i < 16; i++) + hw->ATTR[i] = i; + hw->ATTR[16] = 0x41; + hw->ATTR[17] = 0xFF; + hw->ATTR[18] = 0x0F; + hw->ATTR[19] = 0x00; + hw->ATTR[20] = 0x00; + + hd = m->HDisplay >> 3; + hs = m->HSyncStart >> 3; + he = m->HSyncEnd >> 3; + ht = m->HTotal >> 3; + /* standard timmings are in 8pixels, but for interleaved we cannot */ + /* do it for 4bpp (because of (4bpp >> 1(interleaved))/4 == 0) */ + /* using 16 or more pixels per unit can save us */ + divider = minfo->curr.final_bppShift; + while (divider & 3) { + hd >>= 1; + hs >>= 1; + he >>= 1; + ht >>= 1; + divider <<= 1; + } + divider = divider / 4; + /* divider can be from 1 to 8 */ + while (divider > 8) { + hd <<= 1; + hs <<= 1; + he <<= 1; + ht <<= 1; + divider >>= 1; + } + hd = hd - 1; + hs = hs - 1; + he = he - 1; + ht = ht - 1; + vd = m->VDisplay - 1; + vs = m->VSyncStart - 1; + ve = m->VSyncEnd - 1; + vt = m->VTotal - 2; + lc = vd; + /* G200 cannot work with (ht & 7) == 6 */ + if (((ht & 0x07) == 0x06) || ((ht & 0x0F) == 0x04)) + ht++; + hbe = ht; + wd = minfo->fbcon.var.xres_virtual * minfo->curr.final_bppShift / 64; + + hw->CRTCEXT[0] = 0; + hw->CRTCEXT[5] = 0; + if (m->interlaced) { + hw->CRTCEXT[0] = 0x80; + hw->CRTCEXT[5] = (hs + he - ht) >> 1; + if (!m->dblscan) + wd <<= 1; + vt &= ~1; + } + hw->CRTCEXT[0] |= (wd & 0x300) >> 4; + hw->CRTCEXT[1] = (((ht - 4) & 0x100) >> 8) | + ((hd & 0x100) >> 7) | /* blanking */ + ((hs & 0x100) >> 6) | /* sync start */ + (hbe & 0x040); /* end hor. blanking */ + /* FIXME: Enable vidrst only on G400, and only if TV-out is used */ + if (minfo->outputs[1].src == MATROXFB_SRC_CRTC1) + hw->CRTCEXT[1] |= 0x88; /* enable horizontal and vertical vidrst */ + hw->CRTCEXT[2] = ((vt & 0xC00) >> 10) | + ((vd & 0x400) >> 8) | /* disp end */ + ((vd & 0xC00) >> 7) | /* vblanking start */ + ((vs & 0xC00) >> 5) | + ((lc & 0x400) >> 3); + hw->CRTCEXT[3] = (divider - 1) | 0x80; + hw->CRTCEXT[4] = 0; + + hw->CRTC[0] = ht-4; + hw->CRTC[1] = hd; + hw->CRTC[2] = hd; + hw->CRTC[3] = (hbe & 0x1F) | 0x80; + hw->CRTC[4] = hs; + hw->CRTC[5] = ((hbe & 0x20) << 2) | (he & 0x1F); + hw->CRTC[6] = vt & 0xFF; + hw->CRTC[7] = ((vt & 0x100) >> 8) | + ((vd & 0x100) >> 7) | + ((vs & 0x100) >> 6) | + ((vd & 0x100) >> 5) | + ((lc & 0x100) >> 4) | + ((vt & 0x200) >> 4) | + ((vd & 0x200) >> 3) | + ((vs & 0x200) >> 2); + hw->CRTC[8] = 0x00; + hw->CRTC[9] = ((vd & 0x200) >> 4) | + ((lc & 0x200) >> 3); + if (m->dblscan && !m->interlaced) + hw->CRTC[9] |= 0x80; + for (i = 10; i < 16; i++) + hw->CRTC[i] = 0x00; + hw->CRTC[16] = vs /* & 0xFF */; + hw->CRTC[17] = (ve & 0x0F) | 0x20; + hw->CRTC[18] = vd /* & 0xFF */; + hw->CRTC[19] = wd /* & 0xFF */; + hw->CRTC[20] = 0x00; + hw->CRTC[21] = vd /* & 0xFF */; + hw->CRTC[22] = (vt + 1) /* & 0xFF */; + hw->CRTC[23] = 0xC3; + hw->CRTC[24] = lc; + return 0; +}; + +void matroxfb_vgaHWrestore(struct matrox_fb_info *minfo) +{ + int i; + struct matrox_hw_state * const hw = &minfo->hw; + CRITFLAGS + + DBG(__func__) + + dprintk(KERN_INFO "MiscOutReg: %02X\n", hw->MiscOutReg); + dprintk(KERN_INFO "SEQ regs: "); + for (i = 0; i < 5; i++) + dprintk("%02X:", hw->SEQ[i]); + dprintk("\n"); + dprintk(KERN_INFO "GDC regs: "); + for (i = 0; i < 9; i++) + dprintk("%02X:", hw->GCTL[i]); + dprintk("\n"); + dprintk(KERN_INFO "CRTC regs: "); + for (i = 0; i < 25; i++) + dprintk("%02X:", hw->CRTC[i]); + dprintk("\n"); + dprintk(KERN_INFO "ATTR regs: "); + for (i = 0; i < 21; i++) + dprintk("%02X:", hw->ATTR[i]); + dprintk("\n"); + + CRITBEGIN + + mga_inb(M_ATTR_RESET); + mga_outb(M_ATTR_INDEX, 0); + mga_outb(M_MISC_REG, hw->MiscOutReg); + for (i = 1; i < 5; i++) + mga_setr(M_SEQ_INDEX, i, hw->SEQ[i]); + mga_setr(M_CRTC_INDEX, 17, hw->CRTC[17] & 0x7F); + for (i = 0; i < 25; i++) + mga_setr(M_CRTC_INDEX, i, hw->CRTC[i]); + for (i = 0; i < 9; i++) + mga_setr(M_GRAPHICS_INDEX, i, hw->GCTL[i]); + for (i = 0; i < 21; i++) { + mga_inb(M_ATTR_RESET); + mga_outb(M_ATTR_INDEX, i); + mga_outb(M_ATTR_INDEX, hw->ATTR[i]); + } + mga_outb(M_PALETTE_MASK, 0xFF); + mga_outb(M_DAC_REG, 0x00); + for (i = 0; i < 768; i++) + mga_outb(M_DAC_VAL, hw->DACpal[i]); + mga_inb(M_ATTR_RESET); + mga_outb(M_ATTR_INDEX, 0x20); + + CRITEND +} + +static void get_pins(unsigned char __iomem* pins, struct matrox_bios* bd) { + unsigned int b0 = readb(pins); + + if (b0 == 0x2E && readb(pins+1) == 0x41) { + unsigned int pins_len = readb(pins+2); + unsigned int i; + unsigned char cksum; + unsigned char* dst = bd->pins; + + if (pins_len < 3 || pins_len > 128) { + return; + } + *dst++ = 0x2E; + *dst++ = 0x41; + *dst++ = pins_len; + cksum = 0x2E + 0x41 + pins_len; + for (i = 3; i < pins_len; i++) { + cksum += *dst++ = readb(pins+i); + } + if (cksum) { + return; + } + bd->pins_len = pins_len; + } else if (b0 == 0x40 && readb(pins+1) == 0x00) { + unsigned int i; + unsigned char* dst = bd->pins; + + *dst++ = 0x40; + *dst++ = 0; + for (i = 2; i < 0x40; i++) { + *dst++ = readb(pins+i); + } + bd->pins_len = 0x40; + } +} + +static void get_bios_version(unsigned char __iomem * vbios, struct matrox_bios* bd) { + unsigned int pcir_offset; + + pcir_offset = readb(vbios + 24) | (readb(vbios + 25) << 8); + if (pcir_offset >= 26 && pcir_offset < 0xFFE0 && + readb(vbios + pcir_offset ) == 'P' && + readb(vbios + pcir_offset + 1) == 'C' && + readb(vbios + pcir_offset + 2) == 'I' && + readb(vbios + pcir_offset + 3) == 'R') { + unsigned char h; + + h = readb(vbios + pcir_offset + 0x12); + bd->version.vMaj = (h >> 4) & 0xF; + bd->version.vMin = h & 0xF; + bd->version.vRev = readb(vbios + pcir_offset + 0x13); + } else { + unsigned char h; + + h = readb(vbios + 5); + bd->version.vMaj = (h >> 4) & 0xF; + bd->version.vMin = h & 0xF; + bd->version.vRev = 0; + } +} + +static void get_bios_output(unsigned char __iomem* vbios, struct matrox_bios* bd) { + unsigned char b; + + b = readb(vbios + 0x7FF1); + if (b == 0xFF) { + b = 0; + } + bd->output.state = b; +} + +static void get_bios_tvout(unsigned char __iomem* vbios, struct matrox_bios* bd) { + unsigned int i; + + /* Check for 'IBM .*(V....TVO' string - it means TVO BIOS */ + bd->output.tvout = 0; + if (readb(vbios + 0x1D) != 'I' || + readb(vbios + 0x1E) != 'B' || + readb(vbios + 0x1F) != 'M' || + readb(vbios + 0x20) != ' ') { + return; + } + for (i = 0x2D; i < 0x2D + 128; i++) { + unsigned char b = readb(vbios + i); + + if (b == '(' && readb(vbios + i + 1) == 'V') { + if (readb(vbios + i + 6) == 'T' && + readb(vbios + i + 7) == 'V' && + readb(vbios + i + 8) == 'O') { + bd->output.tvout = 1; + } + return; + } + if (b == 0) + break; + } +} + +static void parse_bios(unsigned char __iomem* vbios, struct matrox_bios* bd) { + unsigned int pins_offset; + + if (readb(vbios) != 0x55 || readb(vbios + 1) != 0xAA) { + return; + } + bd->bios_valid = 1; + get_bios_version(vbios, bd); + get_bios_output(vbios, bd); + get_bios_tvout(vbios, bd); +#if defined(__powerpc__) + /* On PowerPC cards, the PInS offset isn't stored at the end of the + * BIOS image. Instead, you must search the entire BIOS image for + * the magic PInS signature. + * + * This actually applies to all OpenFirmware base cards. Since these + * cards could be put in a MIPS or SPARC system, should the condition + * be something different? + */ + for ( pins_offset = 0 ; pins_offset <= 0xFF80 ; pins_offset++ ) { + unsigned char header[3]; + + header[0] = readb(vbios + pins_offset); + header[1] = readb(vbios + pins_offset + 1); + header[2] = readb(vbios + pins_offset + 2); + if ( (header[0] == 0x2E) && (header[1] == 0x41) + && ((header[2] == 0x40) || (header[2] == 0x80)) ) { + printk(KERN_INFO "PInS data found at offset %u\n", + pins_offset); + get_pins(vbios + pins_offset, bd); + break; + } + } +#else + pins_offset = readb(vbios + 0x7FFC) | (readb(vbios + 0x7FFD) << 8); + if (pins_offset <= 0xFF80) { + get_pins(vbios + pins_offset, bd); + } +#endif +} + +static int parse_pins1(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + unsigned int maxdac; + + switch (bd->pins[22]) { + case 0: maxdac = 175000; break; + case 1: maxdac = 220000; break; + default: maxdac = 240000; break; + } + if (get_unaligned_le16(bd->pins + 24)) { + maxdac = get_unaligned_le16(bd->pins + 24) * 10; + } + minfo->limits.pixel.vcomax = maxdac; + minfo->values.pll.system = get_unaligned_le16(bd->pins + 28) ? + get_unaligned_le16(bd->pins + 28) * 10 : 50000; + /* ignore 4MB, 8MB, module clocks */ + minfo->features.pll.ref_freq = 14318; + minfo->values.reg.mctlwtst = 0x00030101; + return 0; +} + +static void default_pins1(struct matrox_fb_info *minfo) +{ + /* Millennium */ + minfo->limits.pixel.vcomax = 220000; + minfo->values.pll.system = 50000; + minfo->features.pll.ref_freq = 14318; + minfo->values.reg.mctlwtst = 0x00030101; +} + +static int parse_pins2(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = (bd->pins[41] == 0xFF) ? 230000 : ((bd->pins[41] + 100) * 1000); + minfo->values.reg.mctlwtst = ((bd->pins[51] & 0x01) ? 0x00000001 : 0) | + ((bd->pins[51] & 0x02) ? 0x00000100 : 0) | + ((bd->pins[51] & 0x04) ? 0x00010000 : 0) | + ((bd->pins[51] & 0x08) ? 0x00020000 : 0); + minfo->values.pll.system = (bd->pins[43] == 0xFF) ? 50000 : ((bd->pins[43] + 100) * 1000); + minfo->features.pll.ref_freq = 14318; + return 0; +} + +static void default_pins2(struct matrox_fb_info *minfo) +{ + /* Millennium II, Mystique */ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = 230000; + minfo->values.reg.mctlwtst = 0x00030101; + minfo->values.pll.system = 50000; + minfo->features.pll.ref_freq = 14318; +} + +static int parse_pins3(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = (bd->pins[36] == 0xFF) ? 230000 : ((bd->pins[36] + 100) * 1000); + minfo->values.reg.mctlwtst = get_unaligned_le32(bd->pins + 48) == 0xFFFFFFFF ? + 0x01250A21 : get_unaligned_le32(bd->pins + 48); + /* memory config */ + minfo->values.reg.memrdbk = ((bd->pins[57] << 21) & 0x1E000000) | + ((bd->pins[57] << 22) & 0x00C00000) | + ((bd->pins[56] << 1) & 0x000001E0) | + ( bd->pins[56] & 0x0000000F); + minfo->values.reg.opt = (bd->pins[54] & 7) << 10; + minfo->values.reg.opt2 = bd->pins[58] << 12; + minfo->features.pll.ref_freq = (bd->pins[52] & 0x20) ? 14318 : 27000; + return 0; +} + +static void default_pins3(struct matrox_fb_info *minfo) +{ + /* G100, G200 */ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = 230000; + minfo->values.reg.mctlwtst = 0x01250A21; + minfo->values.reg.memrdbk = 0x00000000; + minfo->values.reg.opt = 0x00000C00; + minfo->values.reg.opt2 = 0x00000000; + minfo->features.pll.ref_freq = 27000; +} + +static int parse_pins4(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + minfo->limits.pixel.vcomax = (bd->pins[ 39] == 0xFF) ? 230000 : bd->pins[ 39] * 4000; + minfo->limits.system.vcomax = (bd->pins[ 38] == 0xFF) ? minfo->limits.pixel.vcomax : bd->pins[ 38] * 4000; + minfo->values.reg.mctlwtst = get_unaligned_le32(bd->pins + 71); + minfo->values.reg.memrdbk = ((bd->pins[87] << 21) & 0x1E000000) | + ((bd->pins[87] << 22) & 0x00C00000) | + ((bd->pins[86] << 1) & 0x000001E0) | + ( bd->pins[86] & 0x0000000F); + minfo->values.reg.opt = ((bd->pins[53] << 15) & 0x00400000) | + ((bd->pins[53] << 22) & 0x10000000) | + ((bd->pins[53] << 7) & 0x00001C00); + minfo->values.reg.opt3 = get_unaligned_le32(bd->pins + 67); + minfo->values.pll.system = (bd->pins[ 65] == 0xFF) ? 200000 : bd->pins[ 65] * 4000; + minfo->features.pll.ref_freq = (bd->pins[ 92] & 0x01) ? 14318 : 27000; + return 0; +} + +static void default_pins4(struct matrox_fb_info *minfo) +{ + /* G400 */ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = 252000; + minfo->values.reg.mctlwtst = 0x04A450A1; + minfo->values.reg.memrdbk = 0x000000E7; + minfo->values.reg.opt = 0x10000400; + minfo->values.reg.opt3 = 0x0190A419; + minfo->values.pll.system = 200000; + minfo->features.pll.ref_freq = 27000; +} + +static int parse_pins5(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + unsigned int mult; + + mult = bd->pins[4]?8000:6000; + + minfo->limits.pixel.vcomax = (bd->pins[ 38] == 0xFF) ? 600000 : bd->pins[ 38] * mult; + minfo->limits.system.vcomax = (bd->pins[ 36] == 0xFF) ? minfo->limits.pixel.vcomax : bd->pins[ 36] * mult; + minfo->limits.video.vcomax = (bd->pins[ 37] == 0xFF) ? minfo->limits.system.vcomax : bd->pins[ 37] * mult; + minfo->limits.pixel.vcomin = (bd->pins[123] == 0xFF) ? 256000 : bd->pins[123] * mult; + minfo->limits.system.vcomin = (bd->pins[121] == 0xFF) ? minfo->limits.pixel.vcomin : bd->pins[121] * mult; + minfo->limits.video.vcomin = (bd->pins[122] == 0xFF) ? minfo->limits.system.vcomin : bd->pins[122] * mult; + minfo->values.pll.system = + minfo->values.pll.video = (bd->pins[ 92] == 0xFF) ? 284000 : bd->pins[ 92] * 4000; + minfo->values.reg.opt = get_unaligned_le32(bd->pins + 48); + minfo->values.reg.opt2 = get_unaligned_le32(bd->pins + 52); + minfo->values.reg.opt3 = get_unaligned_le32(bd->pins + 94); + minfo->values.reg.mctlwtst = get_unaligned_le32(bd->pins + 98); + minfo->values.reg.memmisc = get_unaligned_le32(bd->pins + 102); + minfo->values.reg.memrdbk = get_unaligned_le32(bd->pins + 106); + minfo->features.pll.ref_freq = (bd->pins[110] & 0x01) ? 14318 : 27000; + minfo->values.memory.ddr = (bd->pins[114] & 0x60) == 0x20; + minfo->values.memory.dll = (bd->pins[115] & 0x02) != 0; + minfo->values.memory.emrswen = (bd->pins[115] & 0x01) != 0; + minfo->values.reg.maccess = minfo->values.memory.emrswen ? 0x00004000 : 0x00000000; + if (bd->pins[115] & 4) { + minfo->values.reg.mctlwtst_core = minfo->values.reg.mctlwtst; + } else { + static const u8 wtst_xlat[] = { + 0, 1, 5, 6, 7, 5, 2, 3 + }; + + minfo->values.reg.mctlwtst_core = (minfo->values.reg.mctlwtst & ~7) | + wtst_xlat[minfo->values.reg.mctlwtst & 7]; + } + minfo->max_pixel_clock_panellink = bd->pins[47] * 4000; + return 0; +} + +static void default_pins5(struct matrox_fb_info *minfo) +{ + /* Mine 16MB G450 with SDRAM DDR */ + minfo->limits.pixel.vcomax = + minfo->limits.system.vcomax = + minfo->limits.video.vcomax = 600000; + minfo->limits.pixel.vcomin = + minfo->limits.system.vcomin = + minfo->limits.video.vcomin = 256000; + minfo->values.pll.system = + minfo->values.pll.video = 284000; + minfo->values.reg.opt = 0x404A1160; + minfo->values.reg.opt2 = 0x0000AC00; + minfo->values.reg.opt3 = 0x0090A409; + minfo->values.reg.mctlwtst_core = + minfo->values.reg.mctlwtst = 0x0C81462B; + minfo->values.reg.memmisc = 0x80000004; + minfo->values.reg.memrdbk = 0x01001103; + minfo->features.pll.ref_freq = 27000; + minfo->values.memory.ddr = 1; + minfo->values.memory.dll = 1; + minfo->values.memory.emrswen = 1; + minfo->values.reg.maccess = 0x00004000; +} + +static int matroxfb_set_limits(struct matrox_fb_info *minfo, + const struct matrox_bios *bd) +{ + unsigned int pins_version; + static const unsigned int pinslen[] = { 64, 64, 64, 128, 128 }; + + switch (minfo->chip) { + case MGA_2064: default_pins1(minfo); break; + case MGA_2164: + case MGA_1064: + case MGA_1164: default_pins2(minfo); break; + case MGA_G100: + case MGA_G200: default_pins3(minfo); break; + case MGA_G400: default_pins4(minfo); break; + case MGA_G450: + case MGA_G550: default_pins5(minfo); break; + } + if (!bd->bios_valid) { + printk(KERN_INFO "matroxfb: Your Matrox device does not have BIOS\n"); + return -1; + } + if (bd->pins_len < 64) { + printk(KERN_INFO "matroxfb: BIOS on your Matrox device does not contain powerup info\n"); + return -1; + } + if (bd->pins[0] == 0x2E && bd->pins[1] == 0x41) { + pins_version = bd->pins[5]; + if (pins_version < 2 || pins_version > 5) { + printk(KERN_INFO "matroxfb: Unknown version (%u) of powerup info\n", pins_version); + return -1; + } + } else { + pins_version = 1; + } + if (bd->pins_len != pinslen[pins_version - 1]) { + printk(KERN_INFO "matroxfb: Invalid powerup info\n"); + return -1; + } + switch (pins_version) { + case 1: + return parse_pins1(minfo, bd); + case 2: + return parse_pins2(minfo, bd); + case 3: + return parse_pins3(minfo, bd); + case 4: + return parse_pins4(minfo, bd); + case 5: + return parse_pins5(minfo, bd); + default: + printk(KERN_DEBUG "matroxfb: Powerup info version %u is not yet supported\n", pins_version); + return -1; + } +} + +void matroxfb_read_pins(struct matrox_fb_info *minfo) +{ + u32 opt; + u32 biosbase; + u32 fbbase; + struct pci_dev *pdev = minfo->pcidev; + + memset(&minfo->bios, 0, sizeof(minfo->bios)); + pci_read_config_dword(pdev, PCI_OPTION_REG, &opt); + pci_write_config_dword(pdev, PCI_OPTION_REG, opt | PCI_OPTION_ENABLE_ROM); + pci_read_config_dword(pdev, PCI_ROM_ADDRESS, &biosbase); + pci_read_config_dword(pdev, minfo->devflags.fbResource, &fbbase); + pci_write_config_dword(pdev, PCI_ROM_ADDRESS, (fbbase & PCI_ROM_ADDRESS_MASK) | PCI_ROM_ADDRESS_ENABLE); + parse_bios(vaddr_va(minfo->video.vbase), &minfo->bios); + pci_write_config_dword(pdev, PCI_ROM_ADDRESS, biosbase); + pci_write_config_dword(pdev, PCI_OPTION_REG, opt); +#ifdef CONFIG_X86 + if (!minfo->bios.bios_valid) { + unsigned char __iomem* b; + + b = ioremap(0x000C0000, 65536); + if (!b) { + printk(KERN_INFO "matroxfb: Unable to map legacy BIOS\n"); + } else { + unsigned int ven = readb(b+0x64+0) | (readb(b+0x64+1) << 8); + unsigned int dev = readb(b+0x64+2) | (readb(b+0x64+3) << 8); + + if (ven != pdev->vendor || dev != pdev->device) { + printk(KERN_INFO "matroxfb: Legacy BIOS is for %04X:%04X, while this device is %04X:%04X\n", + ven, dev, pdev->vendor, pdev->device); + } else { + parse_bios(b, &minfo->bios); + } + iounmap(b); + } + } +#endif + matroxfb_set_limits(minfo, &minfo->bios); + printk(KERN_INFO "PInS memtype = %u\n", + (minfo->values.reg.opt & 0x1C00) >> 10); +} + +EXPORT_SYMBOL(matroxfb_DAC_in); +EXPORT_SYMBOL(matroxfb_DAC_out); +EXPORT_SYMBOL(matroxfb_var2my); +EXPORT_SYMBOL(matroxfb_PLL_calcclock); +EXPORT_SYMBOL(matroxfb_vgaHWinit); /* DAC1064, Ti3026 */ +EXPORT_SYMBOL(matroxfb_vgaHWrestore); /* DAC1064, Ti3026 */ +EXPORT_SYMBOL(matroxfb_read_pins); + +MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); +MODULE_DESCRIPTION("Miscellaneous support for Matrox video cards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/matrox/matroxfb_misc.h b/drivers/video/fbdev/matrox/matroxfb_misc.h new file mode 100644 index 000000000..9cb6686d3 --- /dev/null +++ b/drivers/video/fbdev/matrox/matroxfb_misc.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MATROXFB_MISC_H__ +#define __MATROXFB_MISC_H__ + +#include "matroxfb_base.h" + +/* also for modules */ +int matroxfb_PLL_calcclock(const struct matrox_pll_features* pll, unsigned int freq, unsigned int fmax, + unsigned int* in, unsigned int* feed, unsigned int* post); +static inline int PLL_calcclock(const struct matrox_fb_info *minfo, + unsigned int freq, unsigned int fmax, + unsigned int *in, unsigned int *feed, + unsigned int *post) +{ + return matroxfb_PLL_calcclock(&minfo->features.pll, freq, fmax, in, feed, post); +} + +int matroxfb_vgaHWinit(struct matrox_fb_info *minfo, struct my_timming* m); +void matroxfb_vgaHWrestore(struct matrox_fb_info *minfo); +void matroxfb_read_pins(struct matrox_fb_info *minfo); + +#endif /* __MATROXFB_MISC_H__ */ |
