pmagb-b-fb.c 9.7 KB
Newer Older
L
Linus Torvalds 已提交
1
/*
2
 *	linux/drivers/video/pmagb-b-fb.c
L
Linus Torvalds 已提交
3
 *
4 5
 *	PMAGB-B TURBOchannel Smart Frame Buffer (SFB) card support,
 *	derived from:
L
Linus Torvalds 已提交
6 7
 *	"HP300 Topcat framebuffer support (derived from macfb of all things)
 *	Phil Blundell <philb@gnu.org> 1998", the original code can be
8
 *	found in the file hpfb.c in the same directory.
L
Linus Torvalds 已提交
9
 *
10 11 12
 *	DECstation related code Copyright (C) 1999, 2000, 2001 by
 *	Michael Engel <engel@unix-ag.org>,
 *	Karsten Merker <merker@linuxtag.org> and
L
Linus Torvalds 已提交
13
 *	Harald Koerfgen.
14
 *	Copyright (c) 2005, 2006  Maciej W. Rozycki
L
Linus Torvalds 已提交
15
 *
16 17 18
 *	This file is subject to the terms and conditions of the GNU General
 *	Public License.  See the file COPYING in the main directory of this
 *	archive for more details.
L
Linus Torvalds 已提交
19 20
 */

21
#include <linux/compiler.h>
L
Linus Torvalds 已提交
22
#include <linux/delay.h>
23
#include <linux/errno.h>
L
Linus Torvalds 已提交
24
#include <linux/fb.h>
25 26 27
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
28
#include <linux/tc.h>
29 30 31 32 33
#include <linux/types.h>

#include <asm/io.h>
#include <asm/system.h>

L
Linus Torvalds 已提交
34 35
#include <video/pmagb-b-fb.h>

36 37 38 39 40 41 42 43 44

struct pmagbbfb_par {
	volatile void __iomem *mmio;
	volatile void __iomem *smem;
	volatile u32 __iomem *sfb;
	volatile u32 __iomem *dac;
	unsigned int osc0;
	unsigned int osc1;
	int slot;
L
Linus Torvalds 已提交
45 46 47
};


48
static struct fb_var_screeninfo pmagbbfb_defined __initdata = {
L
Linus Torvalds 已提交
49 50 51 52 53
	.bits_per_pixel	= 8,
	.red.length	= 8,
	.green.length	= 8,
	.blue.length	= 8,
	.activate	= FB_ACTIVATE_NOW,
54 55
	.height		= -1,
	.width		= -1,
L
Linus Torvalds 已提交
56
	.accel_flags	= FB_ACCEL_NONE,
57
	.sync		= FB_SYNC_ON_GREEN,
L
Linus Torvalds 已提交
58 59 60
	.vmode		= FB_VMODE_NONINTERLACED,
};

61
static struct fb_fix_screeninfo pmagbbfb_fix __initdata = {
L
Linus Torvalds 已提交
62
	.id		= "PMAGB-BA",
63
	.smem_len	= (2048 * 1024),
L
Linus Torvalds 已提交
64 65
	.type		= FB_TYPE_PACKED_PIXELS,
	.visual		= FB_VISUAL_PSEUDOCOLOR,
66 67 68 69 70 71 72
	.mmio_len	= PMAGB_B_FBMEM,
};


static inline void sfb_write(struct pmagbbfb_par *par, unsigned int reg, u32 v)
{
	writel(v, par->sfb + reg / 4);
L
Linus Torvalds 已提交
73 74
}

75 76 77 78 79 80
static inline u32 sfb_read(struct pmagbbfb_par *par, unsigned int reg)
{
	return readl(par->sfb + reg / 4);
}

static inline void dac_write(struct pmagbbfb_par *par, unsigned int reg, u8 v)
L
Linus Torvalds 已提交
81
{
82
	writeb(v, par->dac + reg / 4);
L
Linus Torvalds 已提交
83 84
}

85 86 87 88 89 90 91 92 93 94 95
static inline u8 dac_read(struct pmagbbfb_par *par, unsigned int reg)
{
	return readb(par->dac + reg / 4);
}

static inline void gp0_write(struct pmagbbfb_par *par, u32 v)
{
	writel(v, par->mmio + PMAGB_B_GP0);
}


L
Linus Torvalds 已提交
96
/*
97
 * Set the palette.
L
Linus Torvalds 已提交
98
 */
99 100 101
static int pmagbbfb_setcolreg(unsigned int regno, unsigned int red,
			      unsigned int green, unsigned int blue,
			      unsigned int transp, struct fb_info *info)
L
Linus Torvalds 已提交
102
{
103 104 105
	struct pmagbbfb_par *par = info->par;

	BUG_ON(regno >= info->cmap.len);
L
Linus Torvalds 已提交
106 107

	red   >>= 8;	/* The cmap fields are 16 bits    */
108
	green >>= 8;	/* wide, but the hardware colormap */
L
Linus Torvalds 已提交
109 110
	blue  >>= 8;	/* registers are only 8 bits wide */

111 112 113 114 115 116 117 118 119 120
	mb();
	dac_write(par, BT459_ADDR_LO, regno);
	dac_write(par, BT459_ADDR_HI, 0x00);
	wmb();
	dac_write(par, BT459_CMAP, red);
	wmb();
	dac_write(par, BT459_CMAP, green);
	wmb();
	dac_write(par, BT459_CMAP, blue);

L
Linus Torvalds 已提交
121 122 123 124 125 126 127 128 129 130 131
	return 0;
}

static struct fb_ops pmagbbfb_ops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= pmagbbfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};

132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

/*
 * Turn the hardware cursor off.
 */
static void __init pmagbbfb_erase_cursor(struct fb_info *info)
{
	struct pmagbbfb_par *par = info->par;

	mb();
	dac_write(par, BT459_ADDR_LO, 0x00);
	dac_write(par, BT459_ADDR_HI, 0x03);
	wmb();
	dac_write(par, BT459_DATA, 0x00);
}

/*
 * Set up screen parameters.
 */
static void __init pmagbbfb_screen_setup(struct fb_info *info)
{
	struct pmagbbfb_par *par = info->par;

	info->var.xres = ((sfb_read(par, SFB_REG_VID_HOR) >>
			   SFB_VID_HOR_PIX_SHIFT) & SFB_VID_HOR_PIX_MASK) * 4;
	info->var.xres_virtual = info->var.xres;
	info->var.yres = (sfb_read(par, SFB_REG_VID_VER) >>
			  SFB_VID_VER_SL_SHIFT) & SFB_VID_VER_SL_MASK;
	info->var.yres_virtual = info->var.yres;
	info->var.left_margin = ((sfb_read(par, SFB_REG_VID_HOR) >>
				  SFB_VID_HOR_BP_SHIFT) &
				 SFB_VID_HOR_BP_MASK) * 4;
	info->var.right_margin = ((sfb_read(par, SFB_REG_VID_HOR) >>
				   SFB_VID_HOR_FP_SHIFT) &
				  SFB_VID_HOR_FP_MASK) * 4;
	info->var.upper_margin = (sfb_read(par, SFB_REG_VID_VER) >>
				  SFB_VID_VER_BP_SHIFT) & SFB_VID_VER_BP_MASK;
	info->var.lower_margin = (sfb_read(par, SFB_REG_VID_VER) >>
				  SFB_VID_VER_FP_SHIFT) & SFB_VID_VER_FP_MASK;
	info->var.hsync_len = ((sfb_read(par, SFB_REG_VID_HOR) >>
				SFB_VID_HOR_SYN_SHIFT) &
			       SFB_VID_HOR_SYN_MASK) * 4;
	info->var.vsync_len = (sfb_read(par, SFB_REG_VID_VER) >>
			       SFB_VID_VER_SYN_SHIFT) & SFB_VID_VER_SYN_MASK;

	info->fix.line_length = info->var.xres;
};

/*
 * Determine oscillator configuration.
 */
static void __init pmagbbfb_osc_setup(struct fb_info *info)
L
Linus Torvalds 已提交
183
{
184
	static unsigned int pmagbbfb_freqs[] __initdata = {
185
		130808, 119843, 104000, 92980, 74370, 72800,
186 187 188
		69197, 66000, 65000, 50350, 36000, 32000, 25175
	};
	struct pmagbbfb_par *par = info->par;
189
	struct tc_bus *tbus = to_tc_dev(info->device)->bus;
190
	u32 count0 = 8, count1 = 8, counttc = 16 * 256 + 8;
191
	u32 freq0, freq1, freqtc = tc_get_speed(tbus) / 250;
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
	int i, j;

	gp0_write(par, 0);				/* select Osc0 */
	for (j = 0; j < 16; j++) {
		mb();
		sfb_write(par, SFB_REG_TCCLK_COUNT, 0);
		mb();
		for (i = 0; i < 100; i++) {	/* nominally max. 20.5us */
			if (sfb_read(par, SFB_REG_TCCLK_COUNT) == 0)
				break;
			udelay(1);
		}
		count0 += sfb_read(par, SFB_REG_VIDCLK_COUNT);
	}

	gp0_write(par, 1);				/* select Osc1 */
	for (j = 0; j < 16; j++) {
		mb();
		sfb_write(par, SFB_REG_TCCLK_COUNT, 0);

		for (i = 0; i < 100; i++) {	/* nominally max. 20.5us */
			if (sfb_read(par, SFB_REG_TCCLK_COUNT) == 0)
				break;
			udelay(1);
		}
		count1 += sfb_read(par, SFB_REG_VIDCLK_COUNT);
	}

	freq0 = (freqtc * count0 + counttc / 2) / counttc;
	par->osc0 = freq0;
	if (freq0 >= pmagbbfb_freqs[0] - (pmagbbfb_freqs[0] + 32) / 64 &&
	    freq0 <= pmagbbfb_freqs[0] + (pmagbbfb_freqs[0] + 32) / 64)
		par->osc0 = pmagbbfb_freqs[0];

	freq1 = (par->osc0 * count1 + count0 / 2) / count0;
	par->osc1 = freq1;
228
	for (i = 0; i < ARRAY_SIZE(pmagbbfb_freqs); i++)
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
		if (freq1 >= pmagbbfb_freqs[i] -
			     (pmagbbfb_freqs[i] + 128) / 256 &&
		    freq1 <= pmagbbfb_freqs[i] +
			     (pmagbbfb_freqs[i] + 128) / 256) {
			par->osc1 = pmagbbfb_freqs[i];
			break;
		}

	if (par->osc0 - par->osc1 <= (par->osc0 + par->osc1 + 256) / 512 ||
	    par->osc1 - par->osc0 <= (par->osc0 + par->osc1 + 256) / 512)
		par->osc1 = 0;

	gp0_write(par, par->osc1 != 0);			/* reselect OscX */

	info->var.pixclock = par->osc1 ?
			     (1000000000 + par->osc1 / 2) / par->osc1 :
			     (1000000000 + par->osc0 / 2) / par->osc0;
};


249
static int __init pmagbbfb_probe(struct device *dev)
250
{
251 252
	struct tc_dev *tdev = to_tc_dev(dev);
	resource_size_t start, len;
253 254
	struct fb_info *info;
	struct pmagbbfb_par *par;
255
	char freq0[12], freq1[12];
256 257
	u32 vid_base;

258
	info = framebuffer_alloc(sizeof(struct pmagbbfb_par), dev);
259 260 261 262
	if (!info)
		return -ENOMEM;

	par = info->par;
263
	dev_set_drvdata(dev, info);
264 265 266 267

	if (fb_alloc_cmap(&info->cmap, 256, 0) < 0)
		goto err_alloc;

L
Linus Torvalds 已提交
268 269
	info->fbops = &pmagbbfb_ops;
	info->fix = pmagbbfb_fix;
270
	info->var = pmagbbfb_defined;
L
Linus Torvalds 已提交
271 272
	info->flags = FBINFO_DEFAULT;

273 274 275 276 277 278
	/* Request the I/O MEM resource.  */
	start = tdev->resource.start;
	len = tdev->resource.end - start + 1;
	if (!request_mem_region(start, len, dev->bus_id))
		goto err_cmap;

279
	/* MMIO mapping setup.  */
280
	info->fix.mmio_start = start;
281 282
	par->mmio = ioremap_nocache(info->fix.mmio_start, info->fix.mmio_len);
	if (!par->mmio)
283
		goto err_resource;
284 285 286 287
	par->sfb = par->mmio + PMAGB_B_SFB;
	par->dac = par->mmio + PMAGB_B_BT459;

	/* Frame buffer mapping setup.  */
288
	info->fix.smem_start = start + PMAGB_B_FBMEM;
289 290 291 292 293 294 295 296 297 298
	par->smem = ioremap_nocache(info->fix.smem_start, info->fix.smem_len);
	if (!par->smem)
		goto err_mmio_map;
	vid_base = sfb_read(par, SFB_REG_VID_BASE);
	info->screen_base = (void __iomem *)par->smem + vid_base * 0x1000;
	info->screen_size = info->fix.smem_len - 2 * vid_base * 0x1000;

	pmagbbfb_erase_cursor(info);
	pmagbbfb_screen_setup(info);
	pmagbbfb_osc_setup(info);
L
Linus Torvalds 已提交
299 300

	if (register_framebuffer(info) < 0)
301 302
		goto err_smem_map;

303 304
	get_device(dev);

305 306 307 308 309
	snprintf(freq0, sizeof(freq0), "%u.%03uMHz",
		 par->osc0 / 1000, par->osc0 % 1000);
	snprintf(freq1, sizeof(freq1), "%u.%03uMHz",
		 par->osc1 / 1000, par->osc1 % 1000);

310 311
	pr_info("fb%d: %s frame buffer device at %s\n",
		info->node, info->fix.id, dev->bus_id);
312 313 314 315
	pr_info("fb%d: Osc0: %s, Osc1: %s, Osc%u selected\n",
		info->node, freq0, par->osc1 ? freq1 : "disabled",
		par->osc1 != 0);

L
Linus Torvalds 已提交
316
	return 0;
317 318 319 320 321 322 323 324


err_smem_map:
	iounmap(par->smem);

err_mmio_map:
	iounmap(par->mmio);

325 326 327
err_resource:
	release_mem_region(start, len);

328 329 330 331 332 333
err_cmap:
	fb_dealloc_cmap(&info->cmap);

err_alloc:
	framebuffer_release(info);
	return -ENXIO;
L
Linus Torvalds 已提交
334 335
}

336
static int __exit pmagbbfb_remove(struct device *dev)
337
{
338 339
	struct tc_dev *tdev = to_tc_dev(dev);
	struct fb_info *info = dev_get_drvdata(dev);
340
	struct pmagbbfb_par *par = info->par;
341
	resource_size_t start, len;
342

343
	put_device(dev);
344 345 346
	unregister_framebuffer(info);
	iounmap(par->smem);
	iounmap(par->mmio);
347 348 349
	start = tdev->resource.start;
	len = tdev->resource.end - start + 1;
	release_mem_region(start, len);
350 351
	fb_dealloc_cmap(&info->cmap);
	framebuffer_release(info);
352
	return 0;
353
}
L
Linus Torvalds 已提交
354

355 356

/*
357
 * Initialize the framebuffer.
358
 */
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
static const struct tc_device_id pmagbbfb_tc_table[] = {
	{ "DEC     ", "PMAGB-BA" },
	{ }
};
MODULE_DEVICE_TABLE(tc, pmagbbfb_tc_table);

static struct tc_driver pmagbbfb_driver = {
	.id_table	= pmagbbfb_tc_table,
	.driver		= {
		.name	= "pmagbbfb",
		.bus	= &tc_bus_type,
		.probe	= pmagbbfb_probe,
		.remove	= __exit_p(pmagbbfb_remove),
	},
};

375
static int __init pmagbbfb_init(void)
L
Linus Torvalds 已提交
376
{
377
#ifndef MODULE
L
Linus Torvalds 已提交
378
	if (fb_get_options("pmagbbfb", NULL))
379
		return -ENXIO;
380 381
#endif
	return tc_register_driver(&pmagbbfb_driver);
382 383 384 385
}

static void __exit pmagbbfb_exit(void)
{
386
	tc_unregister_driver(&pmagbbfb_driver);
L
Linus Torvalds 已提交
387 388
}

389

L
Linus Torvalds 已提交
390
module_init(pmagbbfb_init);
391 392
module_exit(pmagbbfb_exit);

L
Linus Torvalds 已提交
393
MODULE_LICENSE("GPL");