18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * drivers/video/geode/display_gx1.c
48c2ecf20Sopenharmony_ci *   -- Geode GX1 display controller
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright (C) 2005 Arcom Control Systems Ltd.
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Based on AMD's original 2.4 driver:
98c2ecf20Sopenharmony_ci *   Copyright (C) 2004 Advanced Micro Devices, Inc.
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
128c2ecf20Sopenharmony_ci#include <linux/fb.h>
138c2ecf20Sopenharmony_ci#include <linux/delay.h>
148c2ecf20Sopenharmony_ci#include <asm/io.h>
158c2ecf20Sopenharmony_ci#include <asm/div64.h>
168c2ecf20Sopenharmony_ci#include <asm/delay.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#include "geodefb.h"
198c2ecf20Sopenharmony_ci#include "display_gx1.h"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(gx1_conf_reg_lock);
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic u8 gx1_read_conf_reg(u8 reg)
248c2ecf20Sopenharmony_ci{
258c2ecf20Sopenharmony_ci	u8 val, ccr3;
268c2ecf20Sopenharmony_ci	unsigned long flags;
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	spin_lock_irqsave(&gx1_conf_reg_lock, flags);
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	outb(CONFIG_CCR3, 0x22);
318c2ecf20Sopenharmony_ci	ccr3 = inb(0x23);
328c2ecf20Sopenharmony_ci	outb(CONFIG_CCR3, 0x22);
338c2ecf20Sopenharmony_ci	outb(ccr3 | CONFIG_CCR3_MAPEN, 0x23);
348c2ecf20Sopenharmony_ci	outb(reg, 0x22);
358c2ecf20Sopenharmony_ci	val = inb(0x23);
368c2ecf20Sopenharmony_ci	outb(CONFIG_CCR3, 0x22);
378c2ecf20Sopenharmony_ci	outb(ccr3, 0x23);
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&gx1_conf_reg_lock, flags);
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	return val;
428c2ecf20Sopenharmony_ci}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ciunsigned gx1_gx_base(void)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	return (gx1_read_conf_reg(CONFIG_GCR) & 0x03) << 30;
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ciint gx1_frame_buffer_size(void)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	void __iomem *mc_regs;
528c2ecf20Sopenharmony_ci	u32 bank_cfg;
538c2ecf20Sopenharmony_ci	int d;
548c2ecf20Sopenharmony_ci	unsigned dram_size = 0, fb_base;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	mc_regs = ioremap(gx1_gx_base() + 0x8400, 0x100);
578c2ecf20Sopenharmony_ci	if (!mc_regs)
588c2ecf20Sopenharmony_ci		return -ENOMEM;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	/* Calculate the total size of both DIMM0 and DIMM1. */
628c2ecf20Sopenharmony_ci	bank_cfg = readl(mc_regs + MC_BANK_CFG);
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	for (d = 0; d < 2; d++) {
658c2ecf20Sopenharmony_ci		if ((bank_cfg & MC_BCFG_DIMM0_PG_SZ_MASK) != MC_BCFG_DIMM0_PG_SZ_NO_DIMM)
668c2ecf20Sopenharmony_ci			dram_size += 0x400000 << ((bank_cfg & MC_BCFG_DIMM0_SZ_MASK) >> 8);
678c2ecf20Sopenharmony_ci		bank_cfg >>= 16; /* look at DIMM1 next */
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	fb_base = (readl(mc_regs + MC_GBASE_ADD) & MC_GADD_GBADD_MASK) << 19;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	iounmap(mc_regs);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	return dram_size - fb_base;
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic void gx1_set_mode(struct fb_info *info)
788c2ecf20Sopenharmony_ci{
798c2ecf20Sopenharmony_ci	struct geodefb_par *par = info->par;
808c2ecf20Sopenharmony_ci	u32 gcfg, tcfg, ocfg, dclk_div, val;
818c2ecf20Sopenharmony_ci	int hactive, hblankstart, hsyncstart, hsyncend, hblankend, htotal;
828c2ecf20Sopenharmony_ci	int vactive, vblankstart, vsyncstart, vsyncend, vblankend, vtotal;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	/* Unlock the display controller registers. */
858c2ecf20Sopenharmony_ci	readl(par->dc_regs + DC_UNLOCK);
868c2ecf20Sopenharmony_ci	writel(DC_UNLOCK_CODE, par->dc_regs + DC_UNLOCK);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	gcfg = readl(par->dc_regs + DC_GENERAL_CFG);
898c2ecf20Sopenharmony_ci	tcfg = readl(par->dc_regs + DC_TIMING_CFG);
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	/* Blank the display and disable the timing generator. */
928c2ecf20Sopenharmony_ci	tcfg &= ~(DC_TCFG_BLKE | DC_TCFG_TGEN);
938c2ecf20Sopenharmony_ci	writel(tcfg, par->dc_regs + DC_TIMING_CFG);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	/* Wait for pending memory requests before disabling the FIFO load. */
968c2ecf20Sopenharmony_ci	udelay(100);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	/* Disable FIFO load and compression. */
998c2ecf20Sopenharmony_ci	gcfg &= ~(DC_GCFG_DFLE | DC_GCFG_CMPE | DC_GCFG_DECE);
1008c2ecf20Sopenharmony_ci	writel(gcfg, par->dc_regs + DC_GENERAL_CFG);
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	/* Setup DCLK and its divisor. */
1038c2ecf20Sopenharmony_ci	gcfg &= ~DC_GCFG_DCLK_MASK;
1048c2ecf20Sopenharmony_ci	writel(gcfg, par->dc_regs + DC_GENERAL_CFG);
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	par->vid_ops->set_dclk(info);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	dclk_div = DC_GCFG_DCLK_DIV_1; /* FIXME: may need to divide DCLK by 2 sometimes? */
1098c2ecf20Sopenharmony_ci	gcfg |= dclk_div;
1108c2ecf20Sopenharmony_ci	writel(gcfg, par->dc_regs + DC_GENERAL_CFG);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	/* Wait for the clock generatation to settle.  This is needed since
1138c2ecf20Sopenharmony_ci	 * some of the register writes that follow require that clock to be
1148c2ecf20Sopenharmony_ci	 * present. */
1158c2ecf20Sopenharmony_ci	udelay(1000); /* FIXME: seems a little long */
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	/*
1188c2ecf20Sopenharmony_ci	 * Setup new mode.
1198c2ecf20Sopenharmony_ci	 */
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	/* Clear all unused feature bits. */
1228c2ecf20Sopenharmony_ci	gcfg = DC_GCFG_VRDY | dclk_div;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	/* Set FIFO priority (default 6/5) and enable. */
1258c2ecf20Sopenharmony_ci	/* FIXME: increase fifo priority for 1280x1024 modes? */
1268c2ecf20Sopenharmony_ci	gcfg |= (6 << DC_GCFG_DFHPEL_POS) | (5 << DC_GCFG_DFHPSL_POS) | DC_GCFG_DFLE;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	/* FIXME: Set pixel and line double bits if necessary. */
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	/* Framebuffer start offset. */
1318c2ecf20Sopenharmony_ci	writel(0, par->dc_regs + DC_FB_ST_OFFSET);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	/* Line delta and line buffer length. */
1348c2ecf20Sopenharmony_ci	writel(info->fix.line_length >> 2, par->dc_regs + DC_LINE_DELTA);
1358c2ecf20Sopenharmony_ci	writel(((info->var.xres * info->var.bits_per_pixel/8) >> 3) + 2,
1368c2ecf20Sopenharmony_ci	       par->dc_regs + DC_BUF_SIZE);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	/* Output configuration. Enable panel data, set pixel format. */
1398c2ecf20Sopenharmony_ci	ocfg = DC_OCFG_PCKE | DC_OCFG_PDEL | DC_OCFG_PDEH;
1408c2ecf20Sopenharmony_ci	if (info->var.bits_per_pixel == 8) ocfg |= DC_OCFG_8BPP;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	/* Enable timing generator, sync and FP data. */
1438c2ecf20Sopenharmony_ci	tcfg = DC_TCFG_FPPE | DC_TCFG_HSYE | DC_TCFG_VSYE | DC_TCFG_BLKE
1448c2ecf20Sopenharmony_ci		| DC_TCFG_TGEN;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	/* Horizontal and vertical timings. */
1478c2ecf20Sopenharmony_ci	hactive = info->var.xres;
1488c2ecf20Sopenharmony_ci	hblankstart = hactive;
1498c2ecf20Sopenharmony_ci	hsyncstart = hblankstart + info->var.right_margin;
1508c2ecf20Sopenharmony_ci	hsyncend =  hsyncstart + info->var.hsync_len;
1518c2ecf20Sopenharmony_ci	hblankend = hsyncend + info->var.left_margin;
1528c2ecf20Sopenharmony_ci	htotal = hblankend;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	vactive = info->var.yres;
1558c2ecf20Sopenharmony_ci	vblankstart = vactive;
1568c2ecf20Sopenharmony_ci	vsyncstart = vblankstart + info->var.lower_margin;
1578c2ecf20Sopenharmony_ci	vsyncend =  vsyncstart + info->var.vsync_len;
1588c2ecf20Sopenharmony_ci	vblankend = vsyncend + info->var.upper_margin;
1598c2ecf20Sopenharmony_ci	vtotal = vblankend;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	val = (hactive - 1) | ((htotal - 1) << 16);
1628c2ecf20Sopenharmony_ci	writel(val, par->dc_regs + DC_H_TIMING_1);
1638c2ecf20Sopenharmony_ci	val = (hblankstart - 1) | ((hblankend - 1) << 16);
1648c2ecf20Sopenharmony_ci	writel(val, par->dc_regs + DC_H_TIMING_2);
1658c2ecf20Sopenharmony_ci	val = (hsyncstart - 1) | ((hsyncend - 1) << 16);
1668c2ecf20Sopenharmony_ci	writel(val, par->dc_regs + DC_H_TIMING_3);
1678c2ecf20Sopenharmony_ci	writel(val, par->dc_regs + DC_FP_H_TIMING);
1688c2ecf20Sopenharmony_ci	val = (vactive - 1) | ((vtotal - 1) << 16);
1698c2ecf20Sopenharmony_ci	writel(val, par->dc_regs + DC_V_TIMING_1);
1708c2ecf20Sopenharmony_ci	val = (vblankstart - 1) | ((vblankend - 1) << 16);
1718c2ecf20Sopenharmony_ci	writel(val, par->dc_regs + DC_V_TIMING_2);
1728c2ecf20Sopenharmony_ci	val = (vsyncstart - 1) | ((vsyncend - 1) << 16);
1738c2ecf20Sopenharmony_ci	writel(val, par->dc_regs + DC_V_TIMING_3);
1748c2ecf20Sopenharmony_ci	val = (vsyncstart - 2) | ((vsyncend - 2) << 16);
1758c2ecf20Sopenharmony_ci	writel(val, par->dc_regs + DC_FP_V_TIMING);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	/* Write final register values. */
1788c2ecf20Sopenharmony_ci	writel(ocfg, par->dc_regs + DC_OUTPUT_CFG);
1798c2ecf20Sopenharmony_ci	writel(tcfg, par->dc_regs + DC_TIMING_CFG);
1808c2ecf20Sopenharmony_ci	udelay(1000); /* delay after TIMING_CFG. FIXME: perhaps a little long */
1818c2ecf20Sopenharmony_ci	writel(gcfg, par->dc_regs + DC_GENERAL_CFG);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	par->vid_ops->configure_display(info);
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	/* Relock display controller registers */
1868c2ecf20Sopenharmony_ci	writel(0, par->dc_regs + DC_UNLOCK);
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	/* FIXME: write line_length and bpp to Graphics Pipeline GP_BLT_STATUS
1898c2ecf20Sopenharmony_ci	 * register. */
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_cistatic void gx1_set_hw_palette_reg(struct fb_info *info, unsigned regno,
1938c2ecf20Sopenharmony_ci				   unsigned red, unsigned green, unsigned blue)
1948c2ecf20Sopenharmony_ci{
1958c2ecf20Sopenharmony_ci	struct geodefb_par *par = info->par;
1968c2ecf20Sopenharmony_ci	int val;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	/* Hardware palette is in RGB 6-6-6 format. */
1998c2ecf20Sopenharmony_ci	val  = (red   <<  2) & 0x3f000;
2008c2ecf20Sopenharmony_ci	val |= (green >>  4) & 0x00fc0;
2018c2ecf20Sopenharmony_ci	val |= (blue  >> 10) & 0x0003f;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	writel(regno, par->dc_regs + DC_PAL_ADDRESS);
2048c2ecf20Sopenharmony_ci	writel(val, par->dc_regs + DC_PAL_DATA);
2058c2ecf20Sopenharmony_ci}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ciconst struct geode_dc_ops gx1_dc_ops = {
2088c2ecf20Sopenharmony_ci	.set_mode	 = gx1_set_mode,
2098c2ecf20Sopenharmony_ci	.set_palette_reg = gx1_set_hw_palette_reg,
2108c2ecf20Sopenharmony_ci};
211