linux/drivers/video/fbdev/geode/video_gx.c
Thomas Gleixner 2874c5fd28 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152
Based on 1 normalized pattern(s):

  this program is free software you can redistribute it and or modify
  it under the terms of the gnu general public license as published by
  the free software foundation either version 2 of the license or at
  your option any later version

extracted by the scancode license scanner the SPDX license identifier

  GPL-2.0-or-later

has been chosen to replace the boilerplate/reference in 3029 file(s).

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Allison Randal <allison@lohutok.net>
Cc: linux-spdx@vger.kernel.org
Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-30 11:26:32 -07:00

346 lines
9.7 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Geode GX video processor device.
*
* Copyright (C) 2006 Arcom Control Systems Ltd.
*
* Portions from AMD's original 2.4 driver:
* Copyright (C) 2004 Advanced Micro Devices, Inc.
*/
#include <linux/fb.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/delay.h>
#include <asm/msr.h>
#include <linux/cs5535.h>
#include "gxfb.h"
/*
* Tables of register settings for various DOTCLKs.
*/
struct gx_pll_entry {
long pixclock; /* ps */
u32 sys_rstpll_bits;
u32 dotpll_value;
};
#define POSTDIV3 ((u32)MSR_GLCP_SYS_RSTPLL_DOTPOSTDIV3)
#define PREMULT2 ((u32)MSR_GLCP_SYS_RSTPLL_DOTPREMULT2)
#define PREDIV2 ((u32)MSR_GLCP_SYS_RSTPLL_DOTPOSTDIV3)
static const struct gx_pll_entry gx_pll_table_48MHz[] = {
{ 40123, POSTDIV3, 0x00000BF2 }, /* 24.9230 */
{ 39721, 0, 0x00000037 }, /* 25.1750 */
{ 35308, POSTDIV3|PREMULT2, 0x00000B1A }, /* 28.3220 */
{ 31746, POSTDIV3, 0x000002D2 }, /* 31.5000 */
{ 27777, POSTDIV3|PREMULT2, 0x00000FE2 }, /* 36.0000 */
{ 26666, POSTDIV3, 0x0000057A }, /* 37.5000 */
{ 25000, POSTDIV3, 0x0000030A }, /* 40.0000 */
{ 22271, 0, 0x00000063 }, /* 44.9000 */
{ 20202, 0, 0x0000054B }, /* 49.5000 */
{ 20000, 0, 0x0000026E }, /* 50.0000 */
{ 19860, PREMULT2, 0x00000037 }, /* 50.3500 */
{ 18518, POSTDIV3|PREMULT2, 0x00000B0D }, /* 54.0000 */
{ 17777, 0, 0x00000577 }, /* 56.2500 */
{ 17733, 0, 0x000007F7 }, /* 56.3916 */
{ 17653, 0, 0x0000057B }, /* 56.6444 */
{ 16949, PREMULT2, 0x00000707 }, /* 59.0000 */
{ 15873, POSTDIV3|PREMULT2, 0x00000B39 }, /* 63.0000 */
{ 15384, POSTDIV3|PREMULT2, 0x00000B45 }, /* 65.0000 */
{ 14814, POSTDIV3|PREMULT2, 0x00000FC1 }, /* 67.5000 */
{ 14124, POSTDIV3, 0x00000561 }, /* 70.8000 */
{ 13888, POSTDIV3, 0x000007E1 }, /* 72.0000 */
{ 13426, PREMULT2, 0x00000F4A }, /* 74.4810 */
{ 13333, 0, 0x00000052 }, /* 75.0000 */
{ 12698, 0, 0x00000056 }, /* 78.7500 */
{ 12500, POSTDIV3|PREMULT2, 0x00000709 }, /* 80.0000 */
{ 11135, PREMULT2, 0x00000262 }, /* 89.8000 */
{ 10582, 0, 0x000002D2 }, /* 94.5000 */
{ 10101, PREMULT2, 0x00000B4A }, /* 99.0000 */
{ 10000, PREMULT2, 0x00000036 }, /* 100.0000 */
{ 9259, 0, 0x000007E2 }, /* 108.0000 */
{ 8888, 0, 0x000007F6 }, /* 112.5000 */
{ 7692, POSTDIV3|PREMULT2, 0x00000FB0 }, /* 130.0000 */
{ 7407, POSTDIV3|PREMULT2, 0x00000B50 }, /* 135.0000 */
{ 6349, 0, 0x00000055 }, /* 157.5000 */
{ 6172, 0, 0x000009C1 }, /* 162.0000 */
{ 5787, PREMULT2, 0x0000002D }, /* 172.798 */
{ 5698, 0, 0x000002C1 }, /* 175.5000 */
{ 5291, 0, 0x000002D1 }, /* 189.0000 */
{ 4938, 0, 0x00000551 }, /* 202.5000 */
{ 4357, 0, 0x0000057D }, /* 229.5000 */
};
static const struct gx_pll_entry gx_pll_table_14MHz[] = {
{ 39721, 0, 0x00000037 }, /* 25.1750 */
{ 35308, 0, 0x00000B7B }, /* 28.3220 */
{ 31746, 0, 0x000004D3 }, /* 31.5000 */
{ 27777, 0, 0x00000BE3 }, /* 36.0000 */
{ 26666, 0, 0x0000074F }, /* 37.5000 */
{ 25000, 0, 0x0000050B }, /* 40.0000 */
{ 22271, 0, 0x00000063 }, /* 44.9000 */
{ 20202, 0, 0x0000054B }, /* 49.5000 */
{ 20000, 0, 0x0000026E }, /* 50.0000 */
{ 19860, 0, 0x000007C3 }, /* 50.3500 */
{ 18518, 0, 0x000007E3 }, /* 54.0000 */
{ 17777, 0, 0x00000577 }, /* 56.2500 */
{ 17733, 0, 0x000002FB }, /* 56.3916 */
{ 17653, 0, 0x0000057B }, /* 56.6444 */
{ 16949, 0, 0x0000058B }, /* 59.0000 */
{ 15873, 0, 0x0000095E }, /* 63.0000 */
{ 15384, 0, 0x0000096A }, /* 65.0000 */
{ 14814, 0, 0x00000BC2 }, /* 67.5000 */
{ 14124, 0, 0x0000098A }, /* 70.8000 */
{ 13888, 0, 0x00000BE2 }, /* 72.0000 */
{ 13333, 0, 0x00000052 }, /* 75.0000 */
{ 12698, 0, 0x00000056 }, /* 78.7500 */
{ 12500, 0, 0x0000050A }, /* 80.0000 */
{ 11135, 0, 0x0000078E }, /* 89.8000 */
{ 10582, 0, 0x000002D2 }, /* 94.5000 */
{ 10101, 0, 0x000011F6 }, /* 99.0000 */
{ 10000, 0, 0x0000054E }, /* 100.0000 */
{ 9259, 0, 0x000007E2 }, /* 108.0000 */
{ 8888, 0, 0x000002FA }, /* 112.5000 */
{ 7692, 0, 0x00000BB1 }, /* 130.0000 */
{ 7407, 0, 0x00000975 }, /* 135.0000 */
{ 6349, 0, 0x00000055 }, /* 157.5000 */
{ 6172, 0, 0x000009C1 }, /* 162.0000 */
{ 5698, 0, 0x000002C1 }, /* 175.5000 */
{ 5291, 0, 0x00000539 }, /* 189.0000 */
{ 4938, 0, 0x00000551 }, /* 202.5000 */
{ 4357, 0, 0x0000057D }, /* 229.5000 */
};
void gx_set_dclk_frequency(struct fb_info *info)
{
const struct gx_pll_entry *pll_table;
int pll_table_len;
int i, best_i;
long min, diff;
u64 dotpll, sys_rstpll;
int timeout = 1000;
/* Rev. 1 Geode GXs use a 14 MHz reference clock instead of 48 MHz. */
if (cpu_data(0).x86_stepping == 1) {
pll_table = gx_pll_table_14MHz;
pll_table_len = ARRAY_SIZE(gx_pll_table_14MHz);
} else {
pll_table = gx_pll_table_48MHz;
pll_table_len = ARRAY_SIZE(gx_pll_table_48MHz);
}
/* Search the table for the closest pixclock. */
best_i = 0;
min = abs(pll_table[0].pixclock - info->var.pixclock);
for (i = 1; i < pll_table_len; i++) {
diff = abs(pll_table[i].pixclock - info->var.pixclock);
if (diff < min) {
min = diff;
best_i = i;
}
}
rdmsrl(MSR_GLCP_SYS_RSTPLL, sys_rstpll);
rdmsrl(MSR_GLCP_DOTPLL, dotpll);
/* Program new M, N and P. */
dotpll &= 0x00000000ffffffffull;
dotpll |= (u64)pll_table[best_i].dotpll_value << 32;
dotpll |= MSR_GLCP_DOTPLL_DOTRESET;
dotpll &= ~MSR_GLCP_DOTPLL_BYPASS;
wrmsrl(MSR_GLCP_DOTPLL, dotpll);
/* Program dividers. */
sys_rstpll &= ~( MSR_GLCP_SYS_RSTPLL_DOTPREDIV2
| MSR_GLCP_SYS_RSTPLL_DOTPREMULT2
| MSR_GLCP_SYS_RSTPLL_DOTPOSTDIV3 );
sys_rstpll |= pll_table[best_i].sys_rstpll_bits;
wrmsrl(MSR_GLCP_SYS_RSTPLL, sys_rstpll);
/* Clear reset bit to start PLL. */
dotpll &= ~(MSR_GLCP_DOTPLL_DOTRESET);
wrmsrl(MSR_GLCP_DOTPLL, dotpll);
/* Wait for LOCK bit. */
do {
rdmsrl(MSR_GLCP_DOTPLL, dotpll);
} while (timeout-- && !(dotpll & MSR_GLCP_DOTPLL_LOCK));
}
static void
gx_configure_tft(struct fb_info *info)
{
struct gxfb_par *par = info->par;
unsigned long val;
unsigned long fp;
/* Set up the DF pad select MSR */
rdmsrl(MSR_GX_MSR_PADSEL, val);
val &= ~MSR_GX_MSR_PADSEL_MASK;
val |= MSR_GX_MSR_PADSEL_TFT;
wrmsrl(MSR_GX_MSR_PADSEL, val);
/* Turn off the panel */
fp = read_fp(par, FP_PM);
fp &= ~FP_PM_P;
write_fp(par, FP_PM, fp);
/* Set timing 1 */
fp = read_fp(par, FP_PT1);
fp &= FP_PT1_VSIZE_MASK;
fp |= info->var.yres << FP_PT1_VSIZE_SHIFT;
write_fp(par, FP_PT1, fp);
/* Timing 2 */
/* Set bits that are always on for TFT */
fp = 0x0F100000;
/* Configure sync polarity */
if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT))
fp |= FP_PT2_VSP;
if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT))
fp |= FP_PT2_HSP;
write_fp(par, FP_PT2, fp);
/* Set the dither control */
write_fp(par, FP_DFC, FP_DFC_NFI);
/* Enable the FP data and power (in case the BIOS didn't) */
fp = read_vp(par, VP_DCFG);
fp |= VP_DCFG_FP_PWR_EN | VP_DCFG_FP_DATA_EN;
write_vp(par, VP_DCFG, fp);
/* Unblank the panel */
fp = read_fp(par, FP_PM);
fp |= FP_PM_P;
write_fp(par, FP_PM, fp);
}
void gx_configure_display(struct fb_info *info)
{
struct gxfb_par *par = info->par;
u32 dcfg, misc;
/* Write the display configuration */
dcfg = read_vp(par, VP_DCFG);
/* Disable hsync and vsync */
dcfg &= ~(VP_DCFG_VSYNC_EN | VP_DCFG_HSYNC_EN);
write_vp(par, VP_DCFG, dcfg);
/* Clear bits from existing mode. */
dcfg &= ~(VP_DCFG_CRT_SYNC_SKW
| VP_DCFG_CRT_HSYNC_POL | VP_DCFG_CRT_VSYNC_POL
| VP_DCFG_VSYNC_EN | VP_DCFG_HSYNC_EN);
/* Set default sync skew. */
dcfg |= VP_DCFG_CRT_SYNC_SKW_DEFAULT;
/* Enable hsync and vsync. */
dcfg |= VP_DCFG_HSYNC_EN | VP_DCFG_VSYNC_EN;
misc = read_vp(par, VP_MISC);
/* Disable gamma correction */
misc |= VP_MISC_GAM_EN;
if (par->enable_crt) {
/* Power up the CRT DACs */
misc &= ~(VP_MISC_APWRDN | VP_MISC_DACPWRDN);
write_vp(par, VP_MISC, misc);
/* Only change the sync polarities if we are running
* in CRT mode. The FP polarities will be handled in
* gxfb_configure_tft */
if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT))
dcfg |= VP_DCFG_CRT_HSYNC_POL;
if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT))
dcfg |= VP_DCFG_CRT_VSYNC_POL;
} else {
/* Power down the CRT DACs if in FP mode */
misc |= (VP_MISC_APWRDN | VP_MISC_DACPWRDN);
write_vp(par, VP_MISC, misc);
}
/* Enable the display logic */
/* Set up the DACS to blank normally */
dcfg |= VP_DCFG_CRT_EN | VP_DCFG_DAC_BL_EN;
/* Enable the external DAC VREF? */
write_vp(par, VP_DCFG, dcfg);
/* Set up the flat panel (if it is enabled) */
if (par->enable_crt == 0)
gx_configure_tft(info);
}
int gx_blank_display(struct fb_info *info, int blank_mode)
{
struct gxfb_par *par = info->par;
u32 dcfg, fp_pm;
int blank, hsync, vsync, crt;
/* CRT power saving modes. */
switch (blank_mode) {
case FB_BLANK_UNBLANK:
blank = 0; hsync = 1; vsync = 1; crt = 1;
break;
case FB_BLANK_NORMAL:
blank = 1; hsync = 1; vsync = 1; crt = 1;
break;
case FB_BLANK_VSYNC_SUSPEND:
blank = 1; hsync = 1; vsync = 0; crt = 1;
break;
case FB_BLANK_HSYNC_SUSPEND:
blank = 1; hsync = 0; vsync = 1; crt = 1;
break;
case FB_BLANK_POWERDOWN:
blank = 1; hsync = 0; vsync = 0; crt = 0;
break;
default:
return -EINVAL;
}
dcfg = read_vp(par, VP_DCFG);
dcfg &= ~(VP_DCFG_DAC_BL_EN | VP_DCFG_HSYNC_EN | VP_DCFG_VSYNC_EN |
VP_DCFG_CRT_EN);
if (!blank)
dcfg |= VP_DCFG_DAC_BL_EN;
if (hsync)
dcfg |= VP_DCFG_HSYNC_EN;
if (vsync)
dcfg |= VP_DCFG_VSYNC_EN;
if (crt)
dcfg |= VP_DCFG_CRT_EN;
write_vp(par, VP_DCFG, dcfg);
/* Power on/off flat panel. */
if (par->enable_crt == 0) {
fp_pm = read_fp(par, FP_PM);
if (blank_mode == FB_BLANK_POWERDOWN)
fp_pm &= ~FP_PM_P;
else
fp_pm |= FP_PM_P;
write_fp(par, FP_PM, fp_pm);
}
return 0;
}