162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * drivers/video/geode/display_gx1.c
462306a36Sopenharmony_ci *   -- Geode GX1 display controller
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2005 Arcom Control Systems Ltd.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Based on AMD's original 2.4 driver:
962306a36Sopenharmony_ci *   Copyright (C) 2004 Advanced Micro Devices, Inc.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci#include <linux/spinlock.h>
1262306a36Sopenharmony_ci#include <linux/fb.h>
1362306a36Sopenharmony_ci#include <linux/delay.h>
1462306a36Sopenharmony_ci#include <asm/io.h>
1562306a36Sopenharmony_ci#include <asm/div64.h>
1662306a36Sopenharmony_ci#include <asm/delay.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "geodefb.h"
1962306a36Sopenharmony_ci#include "display_gx1.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic DEFINE_SPINLOCK(gx1_conf_reg_lock);
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic u8 gx1_read_conf_reg(u8 reg)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	u8 val, ccr3;
2662306a36Sopenharmony_ci	unsigned long flags;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	spin_lock_irqsave(&gx1_conf_reg_lock, flags);
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	outb(CONFIG_CCR3, 0x22);
3162306a36Sopenharmony_ci	ccr3 = inb(0x23);
3262306a36Sopenharmony_ci	outb(CONFIG_CCR3, 0x22);
3362306a36Sopenharmony_ci	outb(ccr3 | CONFIG_CCR3_MAPEN, 0x23);
3462306a36Sopenharmony_ci	outb(reg, 0x22);
3562306a36Sopenharmony_ci	val = inb(0x23);
3662306a36Sopenharmony_ci	outb(CONFIG_CCR3, 0x22);
3762306a36Sopenharmony_ci	outb(ccr3, 0x23);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	spin_unlock_irqrestore(&gx1_conf_reg_lock, flags);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	return val;
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ciunsigned gx1_gx_base(void)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	return (gx1_read_conf_reg(CONFIG_GCR) & 0x03) << 30;
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ciint gx1_frame_buffer_size(void)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	void __iomem *mc_regs;
5262306a36Sopenharmony_ci	u32 bank_cfg;
5362306a36Sopenharmony_ci	int d;
5462306a36Sopenharmony_ci	unsigned dram_size = 0, fb_base;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	mc_regs = ioremap(gx1_gx_base() + 0x8400, 0x100);
5762306a36Sopenharmony_ci	if (!mc_regs)
5862306a36Sopenharmony_ci		return -ENOMEM;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	/* Calculate the total size of both DIMM0 and DIMM1. */
6262306a36Sopenharmony_ci	bank_cfg = readl(mc_regs + MC_BANK_CFG);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	for (d = 0; d < 2; d++) {
6562306a36Sopenharmony_ci		if ((bank_cfg & MC_BCFG_DIMM0_PG_SZ_MASK) != MC_BCFG_DIMM0_PG_SZ_NO_DIMM)
6662306a36Sopenharmony_ci			dram_size += 0x400000 << ((bank_cfg & MC_BCFG_DIMM0_SZ_MASK) >> 8);
6762306a36Sopenharmony_ci		bank_cfg >>= 16; /* look at DIMM1 next */
6862306a36Sopenharmony_ci	}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	fb_base = (readl(mc_regs + MC_GBASE_ADD) & MC_GADD_GBADD_MASK) << 19;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	iounmap(mc_regs);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	return dram_size - fb_base;
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic void gx1_set_mode(struct fb_info *info)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	struct geodefb_par *par = info->par;
8062306a36Sopenharmony_ci	u32 gcfg, tcfg, ocfg, dclk_div, val;
8162306a36Sopenharmony_ci	int hactive, hblankstart, hsyncstart, hsyncend, hblankend, htotal;
8262306a36Sopenharmony_ci	int vactive, vblankstart, vsyncstart, vsyncend, vblankend, vtotal;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	/* Unlock the display controller registers. */
8562306a36Sopenharmony_ci	readl(par->dc_regs + DC_UNLOCK);
8662306a36Sopenharmony_ci	writel(DC_UNLOCK_CODE, par->dc_regs + DC_UNLOCK);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	gcfg = readl(par->dc_regs + DC_GENERAL_CFG);
8962306a36Sopenharmony_ci	tcfg = readl(par->dc_regs + DC_TIMING_CFG);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	/* Blank the display and disable the timing generator. */
9262306a36Sopenharmony_ci	tcfg &= ~(DC_TCFG_BLKE | DC_TCFG_TGEN);
9362306a36Sopenharmony_ci	writel(tcfg, par->dc_regs + DC_TIMING_CFG);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	/* Wait for pending memory requests before disabling the FIFO load. */
9662306a36Sopenharmony_ci	udelay(100);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/* Disable FIFO load and compression. */
9962306a36Sopenharmony_ci	gcfg &= ~(DC_GCFG_DFLE | DC_GCFG_CMPE | DC_GCFG_DECE);
10062306a36Sopenharmony_ci	writel(gcfg, par->dc_regs + DC_GENERAL_CFG);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	/* Setup DCLK and its divisor. */
10362306a36Sopenharmony_ci	gcfg &= ~DC_GCFG_DCLK_MASK;
10462306a36Sopenharmony_ci	writel(gcfg, par->dc_regs + DC_GENERAL_CFG);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	par->vid_ops->set_dclk(info);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	dclk_div = DC_GCFG_DCLK_DIV_1; /* FIXME: may need to divide DCLK by 2 sometimes? */
10962306a36Sopenharmony_ci	gcfg |= dclk_div;
11062306a36Sopenharmony_ci	writel(gcfg, par->dc_regs + DC_GENERAL_CFG);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/* Wait for the clock generatation to settle.  This is needed since
11362306a36Sopenharmony_ci	 * some of the register writes that follow require that clock to be
11462306a36Sopenharmony_ci	 * present. */
11562306a36Sopenharmony_ci	udelay(1000); /* FIXME: seems a little long */
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/*
11862306a36Sopenharmony_ci	 * Setup new mode.
11962306a36Sopenharmony_ci	 */
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	/* Clear all unused feature bits. */
12262306a36Sopenharmony_ci	gcfg = DC_GCFG_VRDY | dclk_div;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* Set FIFO priority (default 6/5) and enable. */
12562306a36Sopenharmony_ci	/* FIXME: increase fifo priority for 1280x1024 modes? */
12662306a36Sopenharmony_ci	gcfg |= (6 << DC_GCFG_DFHPEL_POS) | (5 << DC_GCFG_DFHPSL_POS) | DC_GCFG_DFLE;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* FIXME: Set pixel and line double bits if necessary. */
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/* Framebuffer start offset. */
13162306a36Sopenharmony_ci	writel(0, par->dc_regs + DC_FB_ST_OFFSET);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	/* Line delta and line buffer length. */
13462306a36Sopenharmony_ci	writel(info->fix.line_length >> 2, par->dc_regs + DC_LINE_DELTA);
13562306a36Sopenharmony_ci	writel(((info->var.xres * info->var.bits_per_pixel/8) >> 3) + 2,
13662306a36Sopenharmony_ci	       par->dc_regs + DC_BUF_SIZE);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	/* Output configuration. Enable panel data, set pixel format. */
13962306a36Sopenharmony_ci	ocfg = DC_OCFG_PCKE | DC_OCFG_PDEL | DC_OCFG_PDEH;
14062306a36Sopenharmony_ci	if (info->var.bits_per_pixel == 8) ocfg |= DC_OCFG_8BPP;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	/* Enable timing generator, sync and FP data. */
14362306a36Sopenharmony_ci	tcfg = DC_TCFG_FPPE | DC_TCFG_HSYE | DC_TCFG_VSYE | DC_TCFG_BLKE
14462306a36Sopenharmony_ci		| DC_TCFG_TGEN;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	/* Horizontal and vertical timings. */
14762306a36Sopenharmony_ci	hactive = info->var.xres;
14862306a36Sopenharmony_ci	hblankstart = hactive;
14962306a36Sopenharmony_ci	hsyncstart = hblankstart + info->var.right_margin;
15062306a36Sopenharmony_ci	hsyncend =  hsyncstart + info->var.hsync_len;
15162306a36Sopenharmony_ci	hblankend = hsyncend + info->var.left_margin;
15262306a36Sopenharmony_ci	htotal = hblankend;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	vactive = info->var.yres;
15562306a36Sopenharmony_ci	vblankstart = vactive;
15662306a36Sopenharmony_ci	vsyncstart = vblankstart + info->var.lower_margin;
15762306a36Sopenharmony_ci	vsyncend =  vsyncstart + info->var.vsync_len;
15862306a36Sopenharmony_ci	vblankend = vsyncend + info->var.upper_margin;
15962306a36Sopenharmony_ci	vtotal = vblankend;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	val = (hactive - 1) | ((htotal - 1) << 16);
16262306a36Sopenharmony_ci	writel(val, par->dc_regs + DC_H_TIMING_1);
16362306a36Sopenharmony_ci	val = (hblankstart - 1) | ((hblankend - 1) << 16);
16462306a36Sopenharmony_ci	writel(val, par->dc_regs + DC_H_TIMING_2);
16562306a36Sopenharmony_ci	val = (hsyncstart - 1) | ((hsyncend - 1) << 16);
16662306a36Sopenharmony_ci	writel(val, par->dc_regs + DC_H_TIMING_3);
16762306a36Sopenharmony_ci	writel(val, par->dc_regs + DC_FP_H_TIMING);
16862306a36Sopenharmony_ci	val = (vactive - 1) | ((vtotal - 1) << 16);
16962306a36Sopenharmony_ci	writel(val, par->dc_regs + DC_V_TIMING_1);
17062306a36Sopenharmony_ci	val = (vblankstart - 1) | ((vblankend - 1) << 16);
17162306a36Sopenharmony_ci	writel(val, par->dc_regs + DC_V_TIMING_2);
17262306a36Sopenharmony_ci	val = (vsyncstart - 1) | ((vsyncend - 1) << 16);
17362306a36Sopenharmony_ci	writel(val, par->dc_regs + DC_V_TIMING_3);
17462306a36Sopenharmony_ci	val = (vsyncstart - 2) | ((vsyncend - 2) << 16);
17562306a36Sopenharmony_ci	writel(val, par->dc_regs + DC_FP_V_TIMING);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	/* Write final register values. */
17862306a36Sopenharmony_ci	writel(ocfg, par->dc_regs + DC_OUTPUT_CFG);
17962306a36Sopenharmony_ci	writel(tcfg, par->dc_regs + DC_TIMING_CFG);
18062306a36Sopenharmony_ci	udelay(1000); /* delay after TIMING_CFG. FIXME: perhaps a little long */
18162306a36Sopenharmony_ci	writel(gcfg, par->dc_regs + DC_GENERAL_CFG);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	par->vid_ops->configure_display(info);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	/* Relock display controller registers */
18662306a36Sopenharmony_ci	writel(0, par->dc_regs + DC_UNLOCK);
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	/* FIXME: write line_length and bpp to Graphics Pipeline GP_BLT_STATUS
18962306a36Sopenharmony_ci	 * register. */
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic void gx1_set_hw_palette_reg(struct fb_info *info, unsigned regno,
19362306a36Sopenharmony_ci				   unsigned red, unsigned green, unsigned blue)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	struct geodefb_par *par = info->par;
19662306a36Sopenharmony_ci	int val;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	/* Hardware palette is in RGB 6-6-6 format. */
19962306a36Sopenharmony_ci	val  = (red   <<  2) & 0x3f000;
20062306a36Sopenharmony_ci	val |= (green >>  4) & 0x00fc0;
20162306a36Sopenharmony_ci	val |= (blue  >> 10) & 0x0003f;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	writel(regno, par->dc_regs + DC_PAL_ADDRESS);
20462306a36Sopenharmony_ci	writel(val, par->dc_regs + DC_PAL_DATA);
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ciconst struct geode_dc_ops gx1_dc_ops = {
20862306a36Sopenharmony_ci	.set_mode	 = gx1_set_mode,
20962306a36Sopenharmony_ci	.set_palette_reg = gx1_set_hw_palette_reg,
21062306a36Sopenharmony_ci};
211