/*
* Copyright (c) 2009, 2012 Michael Lorenz
* 2014 Naruaki Etomi
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* A console driver for Permedia 2 graphics controllers
*/
#if BYTE_ORDER == LITTLE_ENDIAN
/*
* XXX
* A temporary workaround for unaligned blits on little endian hardware.
* This makes pm2fb_bitblt() work well on little endian hardware to get
* scrolling right, but not much more. Unaligned blits ( as in, where the lower
* 2 bits of the source and destination X coordinates don't match ) are still
* wrong so the glyph cache is also disabled.
*/
#define BITBLT_LE_WORKAROUND
#endif
for (i = 0; pm2fb_pci_devices[i].vendor; i++) {
if (PCI_PRODUCT(pa->pa_id) == pm2fb_pci_devices[i].product) {
sc->sc_is_pm2 = pm2fb_pci_devices[i].flags ;
break;
}
}
pci_aprint_devinfo(pa, NULL);
/*
* fill in parameters from properties
* if we can't get a usable mode via DDC2 we'll use this to pick one,
* which is why we fill them in with some conservative values that
* hopefully work as a last resort
*/
dict = device_properties(self);
if (!prop_dictionary_get_uint32(dict, "width", &sc->sc_width)) {
aprint_error("%s: no width property\n", device_xname(self));
sc->sc_width = 1024;
}
if (!prop_dictionary_get_uint32(dict, "height", &sc->sc_height)) {
aprint_error("%s: no height property\n", device_xname(self));
sc->sc_height = 768;
}
if (!prop_dictionary_get_uint32(dict, "depth", &sc->sc_depth)) {
aprint_error("%s: no depth property\n", device_xname(self));
sc->sc_depth = 8;
}
/*
* don't look at the linebytes property - The Raptor firmware lies
* about it. Get it from width * depth >> 3 instead.
*/
if (pci_mapreg_map(pa, 0x10, PCI_MAPREG_TYPE_MEM, 0,
&sc->sc_memt, &sc->sc_regh, &sc->sc_reg, &sc->sc_regsize)) {
aprint_error("%s: failed to map registers.\n",
device_xname(sc->sc_dev));
}
/*
* XXX yeah, casting the fb address to uint32_t is formally wrong
* but as far as I know there are no PM2 with 64bit BARs
*/
aprint_normal("%s: %d MB aperture at 0x%08x\n", device_xname(self),
(int)(sc->sc_fbsize >> 20), (uint32_t)sc->sc_fb);
#ifdef PM2FB_DEBUG
/*
* leave some room at the bottom of the screen for various blitter
* tests and in order to make the glyph cache visible
*/
sc->sc_height -= 200;
#endif
if (is_console) {
vcons_init_screen(&sc->vd, &sc->sc_console_screen, 1,
&defattr);
sc->sc_console_screen.scr_flags |= VCONS_SCREEN_IS_STATIC;
/*
* restrict all other mappings to processes with superuser privileges
* or the kernel itself
*/
if (kauth_authorize_machdep(kauth_cred_get(),
KAUTH_MACHDEP_UNMANAGEDMEM,
NULL, NULL, NULL, NULL) != 0) {
aprint_normal("%s: mmap() rejected.\n",
device_xname(sc->sc_dev));
return -1;
}
static void
pm2fb_bitblt(void *cookie, int xs, int ys, int xd, int yd,
int wi, int he, int rop)
{
struct pm2fb_softc *sc = cookie;
uint32_t dir = 0;
int rxd, rwi, rxdelta;
if (yd <= ys) {
dir |= PM2RE_INC_Y;
}
if (xd <= xs) {
dir |= PM2RE_INC_X;
}
pm2fb_wait(sc, 10);
bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_DDA_MODE, 0);
bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_MODE, 0);
bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_ALPHA_MODE, 0);
bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_DITHER_MODE, 0);
if (sc->sc_depth == 8) {
int adjust;
/*
* use packed mode for some extra speed
* this copies 32bit quantities even in 8 bit mode, so we need
* to adjust for cases where the lower two bits in source and
* destination X don't align, and/or where the width isn't a
* multiple of 4
*/
if (rop == 3) {
bus_space_write_4(sc->sc_memt, sc->sc_regh,
PM2_RE_CONFIG,
PM2RECFG_READ_SRC | PM2RECFG_WRITE_EN |
PM2RECFG_ROP_EN | PM2RECFG_PACKED | (rop << 6));
} else {
bus_space_write_4(sc->sc_memt, sc->sc_regh,
PM2_RE_CONFIG,
PM2RECFG_READ_SRC | PM2RECFG_READ_DST |
PM2RECFG_WRITE_EN | PM2RECFG_PACKED |
PM2RECFG_ROP_EN | (rop << 6));
}
rxd = xd >> 2;
rwi = (wi + 7) >> 2;
rxdelta = (xs & 0xffc) - (xd & 0xffc);
/* adjust for non-aligned x */
#ifdef BITBLT_LE_WORKAROUND
/* I have no idea why this seems to work */
adjust = 1;
#else
adjust = ((xd & 3) - (xs & 3));
#endif
bus_space_write_4(sc->sc_memt, sc->sc_regh,
PM2_RE_PACKEDDATA_LIMIT,
(xd << 16) | (xd + wi) | (adjust << 29));
pm2fb_wait(sc, he);
switch (ri->ri_font->stride) {
case 1: {
uint8_t *data8 = data;
uint32_t reg;
for (i = 0; i < he; i++) {
reg = *data8;
bus_space_write_4(sc->sc_memt,
sc->sc_regh,
PM2_RE_BITMASK, reg);
data8++;
}
break;
}
case 2: {
uint16_t *data16 = data;
uint32_t reg;
for (i = 0; i < he; i++) {
reg = *data16;
bus_space_write_4(sc->sc_memt,
sc->sc_regh,
PM2_RE_BITMASK, reg);
data16++;
}
break;
}
}
}
if (attr & 1)
pm2fb_rectfill(sc, x, y + he - 2, wi, 1, fg);
}
}
static void
pm2fb_putchar_aa(void *cookie, int row, int col, u_int c, long attr)
{
struct rasops_info *ri = cookie;
struct wsdisplay_font *font = PICK_FONT(ri, c);
struct vcons_screen *scr = ri->ri_hw;
struct pm2fb_softc *sc = scr->scr_cookie;
uint32_t bg, fg, pixel, /*bg32,*/ fg32, aval;
int i, x, y, wi, he;
int r1, g1, b1, /*r0, g0, b0,*/ fgo/*, bgo*/;
uint8_t *data8;
int rv = GC_NOPE, cnt = 0;
if (sc->sc_mode != WSDISPLAYIO_MODE_EMUL)
return;
if (!CHAR_IN_FONT(c, font))
return;
wi = font->fontwidth;
he = font->fontheight;
bg = ri->ri_devcmap[(attr >> 16) & 0xf];
fg = ri->ri_devcmap[(attr >> 24) & 0xf];
x = ri->ri_xorigin + col * wi;
y = ri->ri_yorigin + row * he;
/* always blit the cell with the background colour */
pm2fb_rectfill(sc, x, y, wi, he, bg);
/* if we draw a whitespace we're done here */
if (c == 0x20) {
if (attr & 1)
pm2fb_rectfill(sc, x, y + he - 2, wi, 1, fg);
return;
}
#ifdef BITBLT_LE_WORKAROUND
rv = GC_NOPE;
#else
rv = glyphcache_try(&sc->sc_gc, c, x, y, attr);
if (rv == GC_OK)
return;
#endif
data8 = WSFONT_GLYPH(c, font);
pm2fb_wait(sc, 7);
bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_MODE, 0);
/*
* XXX
* we *should* be able to get away without reading the framebuffer
* since our background colour is always constant, but for some reason
* that produces random, mostly black background
*/
bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_CONFIG,
PM2RECFG_WRITE_EN | PM2RECFG_READ_DST);
/*
* and now we just hammer the foreground colour and alpha values into
* the upload port
*/
for (i = 0; i < ri->ri_fontscale; i++) {
aval = *data8;
pixel = fg32 | (aval << 24);
cnt++;
bus_space_write_4(sc->sc_memt, sc->sc_regh,
PM2_RE_COLOUR, pixel);
/* zero out the EDID buffer */
memset(sc->sc_edid_data, 0, 128);
/* Some monitors don't respond first time */
i = 0;
ok = 0;
while (!ok && i < 10) {
ddc_read_edid(&sc->sc_i2c, sc->sc_edid_data, 128);
ok = (edid_parse(&sc->sc_edid_data[0], &sc->sc_ei) != -1);
i++;
}
#ifdef PM2FB_DEBUG
printf("i = %d\n", i);
for (i = 0; i < 128; i += 16) {
printf("%02x:", i);
for (j = 0; j < 16; j++)
printf(" %02x", sc->sc_edid_data[i + j]);
printf("\n");
}
#endif
if (ok) {
#ifdef PM2FB_DEBUG
edid_print(&sc->sc_ei);
#endif
/*
* Now pick a mode.
*/
if ((sc->sc_ei.edid_preferred_mode != NULL)) {
struct videomode *m = sc->sc_ei.edid_preferred_mode;
if (MODE_IS_VALID(m)) {
sc->sc_videomode = m;
} else {
aprint_error_dev(sc->sc_dev,
"unable to use preferred mode\n");
}
}
/*
* if we can't use the preferred mode go look for the
* best one we can support
*/
if (sc->sc_videomode == NULL) {
struct videomode *m = sc->sc_ei.edid_modes;
sort_modes(sc->sc_ei.edid_modes,
&sc->sc_ei.edid_preferred_mode,
sc->sc_ei.edid_nmodes);
if (sc->sc_videomode == NULL)
for (int n = 0; n < sc->sc_ei.edid_nmodes; n++)
if (MODE_IS_VALID(&m[n])) {
sc->sc_videomode = &m[n];
break;
}
}
}
if (sc->sc_videomode == NULL) {
/* no EDID data? */
sc->sc_videomode = pick_mode_by_ref(sc->sc_width,
sc->sc_height, 60);
}
if (sc->sc_videomode != NULL) {
pm2fb_set_mode(sc, sc->sc_videomode);
}
}
static int
pm2fb_i2c_read_byte(void *cookie, uint8_t *valp, int flags)
{
return (i2c_bitbang_read_byte(cookie, valp, flags, &pm2fb_i2cbb_ops));
}
static int
pm2fb_i2c_write_byte(void *cookie, uint8_t val, int flags)
{
return (i2c_bitbang_write_byte(cookie, val, flags, &pm2fb_i2cbb_ops));
}
static int
pm2vfb_set_pll(struct pm2fb_softc *sc, int freq)
{
int m, n, p, diff, out_freq, bm = 1, bn = 3, bp = 0,
bdiff = 1000000 /* , bfreq */;
int fi;
uint8_t temp;
for (m = 1; m < 128; m++) {
for (n = 2 * m + 1; n < 256; n++) {
fi = PM2_EXT_CLOCK_FREQ * n / m;
for (p = 0; p < 2; p++) {
out_freq = fi >> (p + 1);
diff = abs(out_freq - freq);
if (diff < bdiff) {
bdiff = diff;
/* bfreq = out_freq; */
bm = m;
bn = n;
bp = p;
}
}
}
}
#if 0
/*
* XXX
* output between switching modes and attaching a wsdisplay will
* go through firmware calls on sparc64 and potentially mess up
* our drawing engine state
*/
DPRINTF("best: %d kHz ( %d off ), %d %d %d\n", bfreq, bdiff, bm, bn, bp);
#endif
temp = pm2fb_read_dac(sc, PM2V_DAC_CLOCK_CONTROL) & 0xfc;
pm2fb_write_dac(sc, PM2V_DAC_CONTROL, 0);
pm2fb_write_dac(sc, PM2V_DAC_CLOCK_A_M, bm);
pm2fb_write_dac(sc, PM2V_DAC_CLOCK_A_N, bn);
pm2fb_write_dac(sc, PM2V_DAC_CLOCK_A_P, bp);
pm2fb_write_dac(sc, PM2V_DAC_CLOCK_CONTROL, temp | 3);
return 0;
}
static int
pm2fb_set_pll(struct pm2fb_softc *sc, int freq)
{
uint8_t reg, bm = 0, bn = 0, bp = 0;
unsigned int m, n, p, fi, diff, out_freq, bdiff = 1000000;
for (n = 2; n < 15; n++) {
for (m = 2 ; m < 256; m++) {
fi = PM2_EXT_CLOCK_FREQ * m / n;
if (fi >= PM2_PLL_FREQ_MIN && fi <= PM2_PLL_FREQ_MAX) {
for (p = 0; p < 5; p++) {
out_freq = fi >> p;
diff = abs(out_freq - freq);
if (diff < bdiff) {
bm = m;
bn = n;
bp = p;
bdiff = diff;
}
}
}
}
}
do {
reg = bus_space_read_1(sc->sc_memt, sc->sc_regh,
PM2_DAC_INDEX_DATA);
} while (reg == PCLK_LOCKED);
return 0;
}
/*
* most of the following was adapted from the xf86-video-glint driver's
* pm2_dac.c (8bpp only)
*/
static void
pm2fb_set_dac(struct pm2fb_softc *sc, const struct videomode *mode)
{
int t1, t2, t3, t4, stride;
uint32_t vclk, tmp;
uint8_t sync = 0;
/* first round up to the next multiple of 32 */
stride = (mode->hdisplay + 31) & ~31;
/* then find the next bigger one that we have partial products for */
while ((partprodPermedia[stride >> 5] == -1) && (stride < 2048)) {
stride += 32;
}
sc->sc_width = mode->hdisplay;
sc->sc_height = mode->vdisplay;
sc->sc_depth = 8;
sc->sc_stride = stride;
aprint_normal_dev(sc->sc_dev, "pm2 using %d x %d in 8 bit, stride %d\n",
sc->sc_width, sc->sc_height, stride);
}
/*
* most of the following was adapted from the xf86-video-glint driver's
* pm2v_dac.c
*/
static void
pm2vfb_set_dac(struct pm2fb_softc *sc, const struct videomode *mode)
{
int t1, t2, t3, t4, stride;
uint32_t vclk;
uint8_t sync = 0;
/* first round up to the next multiple of 32 */
stride = (mode->hdisplay + 31) & ~31;
/* then find the next bigger one that we have partial products for */
while ((partprodPermedia[stride >> 5] == -1) && (stride < 2048)) {
stride += 32;
}
bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_SCREEN_STRIDE,
stride >> 3);