/*
*
* 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 <linuxcomp.h>
#include "matroxfb_base.h"
#include "matroxfb_misc.h"
#include "matroxfb_DAC1064.h"
#include "g450_pll.h"
#include <linux/matroxfb.h>
#include <asm/uaccess.h>
#include <asm/div64.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 (sizeof(g450_controls)/sizeof(g450_controls[0]))
/* 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
(WPMINFO
unsigned int idx
) {
return (int*)((char*)MINFO
+ g450_controls
[idx
].
control);
}
static void tvo_fill_defaults
(WPMINFO2
) {
unsigned int i
;
for (i
= 0; i
< G450CTRLS
; i
++) {
*get_ctrl_ptr
(PMINFO i
) = g450_controls
[i
].
desc.
default_value;
}
}
static int cve2_get_reg
(WPMINFO
int reg
) {
unsigned long flags
;
int val
;
matroxfb_DAC_lock_irqsave
(flags
);
matroxfb_DAC_out
(PMINFO
0x87, reg
);
val
= matroxfb_DAC_in
(PMINFO
0x88);
matroxfb_DAC_unlock_irqrestore
(flags
);
return val
;
}
static void cve2_set_reg
(WPMINFO
int reg
, int val
) {
unsigned long flags
;
matroxfb_DAC_lock_irqsave
(flags
);
matroxfb_DAC_out
(PMINFO
0x87, reg
);
matroxfb_DAC_out
(PMINFO
0x88, val
);
matroxfb_DAC_unlock_irqrestore
(flags
);
}
static void cve2_set_reg10
(WPMINFO
int reg
, int val
) {
unsigned long flags
;
matroxfb_DAC_lock_irqsave
(flags
);
matroxfb_DAC_out
(PMINFO
0x87, reg
);
matroxfb_DAC_out
(PMINFO
0x88, val
>> 2);
matroxfb_DAC_out
(PMINFO
0x87, reg
+ 1);
matroxfb_DAC_out
(PMINFO
0x88, val
& 3);
matroxfb_DAC_unlock_irqrestore
(flags
);
}
static void g450_compute_bwlevel
(CPMINFO
int *bl
, int *wl
) {
int b
= ACCESS_FBINFO
(altout.
tvo_params.
brightness) + BLMIN
;
int c
= ACCESS_FBINFO
(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
;
sprintf26
(p
->name
, "Ctrl #%08X", i
);
return 0;
}
return -EINVAL
;
}
static int g450_set_ctrl
(void* md
, struct v4l2_control
*p
) {
int i
;
MINFO_FROM
(md
);
i
= get_ctrl_id
(p
->id
);
if (i
< 0) return -EINVAL
;
/*
* Check if changed.
*/
if (p
->value
== *get_ctrl_ptr
(PMINFO 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
(PMINFO i
) = p
->value
;
switch (p
->id
) {
case V4L2_CID_BRIGHTNESS
:
case V4L2_CID_CONTRAST
:
{
int blacklevel
, whitelevel
;
g450_compute_bwlevel
(PMINFO
&blacklevel
, &whitelevel
);
cve2_set_reg10
(PMINFO
0x0e, blacklevel
);
cve2_set_reg10
(PMINFO
0x1e, whitelevel
);
}
break;
case V4L2_CID_SATURATION
:
cve2_set_reg
(PMINFO
0x20, p
->value
);
cve2_set_reg
(PMINFO
0x22, p
->value
);
break;
case V4L2_CID_HUE
:
cve2_set_reg
(PMINFO
0x25, p
->value
);
break;
case MATROXFB_CID_TESTOUT
:
{
unsigned char val
= cve2_get_reg
(PMINFO
0x05);
if (p
->value
) val
|= 0x02;
else val
&= ~
0x02;
cve2_set_reg
(PMINFO
0x05, val
);
}
break;
}
return 0;
}
static int g450_get_ctrl
(void* md
, struct v4l2_control
*p
) {
int i
;
MINFO_FROM
(md
);
i
= get_ctrl_id
(p
->id
);
if (i
< 0) return -EINVAL
;
p
->value
= *get_ctrl_ptr
(PMINFO 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
(WPMINFO
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
(PMINFO piic
, M_VIDEO_PLL
);
mt
->mnp
= mnp
;
mt
->pixclock
= g450_mnp2f
(PMINFO 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 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(PMINFO (x), m->regs[(x)])
static void cve2_init_TV
(WPMINFO
const struct mavenregs
* m
) {
int i
;
LR
(0x80);
LR
(0x82); LR
(0x83);
LR
(0x84); LR
(0x85);
cve2_set_reg
(PMINFO
0x3E, 0x01);
for (i
= 0; i
< 0x3E; i
++) {
LR
(i
);
}
cve2_set_reg
(PMINFO
0x3E, 0x00);
}
static int matroxfb_g450_compute
(void* md
, struct my_timming
* mt
) {
MINFO_FROM
(md
);
dprintk
(KERN_DEBUG
"Computing, mode=%u\n", ACCESS_FBINFO
(outputs
[1]).
mode);
if (mt
->crtc
== MATROXFB_SRC_CRTC2
&&
ACCESS_FBINFO
(outputs
[1]).
mode != MATROXFB_OUTPUT_MODE_MONITOR
) {
const struct output_desc
* outd
;
cve2_init_TVdata
(ACCESS_FBINFO
(outputs
[1]).
mode, &ACCESS_FBINFO
(hw
).
maven, &outd
);
{
int blacklevel
, whitelevel
;
g450_compute_bwlevel
(PMINFO
&blacklevel
, &whitelevel
);
ACCESS_FBINFO
(hw
).
maven.
regs[0x0E] = blacklevel
>> 2;
ACCESS_FBINFO
(hw
).
maven.
regs[0x0F] = blacklevel
& 3;
ACCESS_FBINFO
(hw
).
maven.
regs[0x1E] = whitelevel
>> 2;
ACCESS_FBINFO
(hw
).
maven.
regs[0x1F] = whitelevel
& 3;
ACCESS_FBINFO
(hw
).
maven.
regs[0x20] =
ACCESS_FBINFO
(hw
).
maven.
regs[0x22] = ACCESS_FBINFO
(altout.
tvo_params.
saturation);
ACCESS_FBINFO
(hw
).
maven.
regs[0x25] = ACCESS_FBINFO
(altout.
tvo_params.
hue);
if (ACCESS_FBINFO
(altout.
tvo_params.
testout)) {
ACCESS_FBINFO
(hw
).
maven.
regs[0x05] |= 0x02;
}
}
computeRegs
(PMINFO
&ACCESS_FBINFO
(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
(PMINFO mt
->pixclock
, (mt
->crtc
== MATROXFB_SRC_CRTC1
) ? M_PIXEL_PLL_C
: M_VIDEO_PLL
);
mt
->pixclock
= g450_mnp2f
(PMINFO mt
->mnp
);
}
dprintk
(KERN_DEBUG
"Pixclock = %u\n", mt
->pixclock
);
return 0;
}
static int matroxfb_g450_program
(void* md
) {
MINFO_FROM
(md
);
if (ACCESS_FBINFO
(outputs
[1]).
mode != MATROXFB_OUTPUT_MODE_MONITOR
) {
cve2_init_TV
(PMINFO
&ACCESS_FBINFO
(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
) {
MINFO_FROM
(md
);
if (mt
->mnp
< 0) {
mt
->mnp
= matroxfb_g450_setclk
(PMINFO mt
->pixclock
, (mt
->crtc
== MATROXFB_SRC_CRTC1
) ? M_PIXEL_PLL_C
: M_VIDEO_PLL
);
mt
->pixclock
= g450_mnp2f
(PMINFO 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
(WPMINFO2
) {
if (ACCESS_FBINFO
(devflags.
g450dac)) {
down_write
(&ACCESS_FBINFO
(altout.
lock));
tvo_fill_defaults
(PMINFO2
);
ACCESS_FBINFO
(outputs
[1]).
src = MATROXFB_SRC_CRTC1
;
ACCESS_FBINFO
(outputs
[1]).
data = MINFO
;
ACCESS_FBINFO
(outputs
[1]).
output = &matroxfb_g450_altout
;
ACCESS_FBINFO
(outputs
[1]).
mode = MATROXFB_OUTPUT_MODE_MONITOR
;
ACCESS_FBINFO
(outputs
[2]).
src = MATROXFB_SRC_CRTC1
;
ACCESS_FBINFO
(outputs
[2]).
data = MINFO
;
ACCESS_FBINFO
(outputs
[2]).
output = &matroxfb_g450_dvi
;
ACCESS_FBINFO
(outputs
[2]).
mode = MATROXFB_OUTPUT_MODE_MONITOR
;
up_write
(&ACCESS_FBINFO
(altout.
lock));
}
}
void matroxfb_g450_shutdown
(WPMINFO2
) {
if (ACCESS_FBINFO
(devflags.
g450dac)) {
down_write
(&ACCESS_FBINFO
(altout.
lock));
ACCESS_FBINFO
(outputs
[1]).
src = MATROXFB_SRC_NONE
;
ACCESS_FBINFO
(outputs
[1]).
output = NULL
;
ACCESS_FBINFO
(outputs
[1]).
data = NULL
;
ACCESS_FBINFO
(outputs
[1]).
mode = MATROXFB_OUTPUT_MODE_MONITOR
;
ACCESS_FBINFO
(outputs
[2]).
src = MATROXFB_SRC_NONE
;
ACCESS_FBINFO
(outputs
[2]).
output = NULL
;
ACCESS_FBINFO
(outputs
[2]).
data = NULL
;
ACCESS_FBINFO
(outputs
[2]).
mode = MATROXFB_OUTPUT_MODE_MONITOR
;
up_write
(&ACCESS_FBINFO
(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");