aboutsummaryrefslogtreecommitdiffstats
path: root/include/asm-ia64/perfmon.h
blob: 7f3333dd00e44b3ed4e8296728c509e23f4a3922 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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
183
184
185
186
187
188
189
190
191
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/*
 * Copyright (C) 2001-2003 Hewlett-Packard Co
 *               Stephane Eranian <eranian@hpl.hp.com>
 */

#ifndef _ASM_IA64_PERFMON_H
#define _ASM_IA64_PERFMON_H

/*
 * perfmon comamnds supported on all CPU models
 */
#define PFM_WRITE_PMCS		0x01
#define PFM_WRITE_PMDS		0x02
#define PFM_READ_PMDS		0x03
#define PFM_STOP		0x04
#define PFM_START		0x05
#define PFM_ENABLE		0x06 /* obsolete */
#define PFM_DISABLE		0x07 /* obsolete */
#define PFM_CREATE_CONTEXT	0x08
#define PFM_DESTROY_CONTEXT	0x09 /* obsolete use close() */
#define PFM_RESTART		0x0a
#define PFM_PROTECT_CONTEXT	0x0b /* obsolete */
#define PFM_GET_FEATURES	0x0c
#define PFM_DEBUG		0x0d
#define PFM_UNPROTECT_CONTEXT	0x0e /* obsolete */
#define PFM_GET_PMC_RESET_VAL	0x0f
#define PFM_LOAD_CONTEXT	0x10
#define PFM_UNLOAD_CONTEXT	0x11

/*
 * PMU model specific commands (may not be supported on all PMU models)
 */
#define PFM_WRITE_IBRS		0x20
#define PFM_WRITE_DBRS		0x21

/*
 * context flags
 */
#define PFM_FL_NOTIFY_BLOCK    	 0x01	/* block task on user level notifications */
#define PFM_FL_SYSTEM_WIDE	 0x02	/* create a system wide context */
#define PFM_FL_OVFL_NO_MSG	 0x80   /* do not post overflow/end messages for notification */

/*
 * event set flags
 */
#define PFM_SETFL_EXCL_IDLE      0x01   /* exclude idle task (syswide only) XXX: DO NOT USE YET */

/*
 * PMC flags
 */
#define PFM_REGFL_OVFL_NOTIFY	0x1	/* send notification on overflow */
#define PFM_REGFL_RANDOM	0x2	/* randomize sampling interval   */

/*
 * PMD/PMC/IBR/DBR return flags (ignored on input)
 *
 * Those flags are used on output and must be checked in case EAGAIN is returned
 * by any of the calls using a pfarg_reg_t or pfarg_dbreg_t structure.
 */
#define PFM_REG_RETFL_NOTAVAIL	(1UL<<31) /* set if register is implemented but not available */
#define PFM_REG_RETFL_EINVAL	(1UL<<30) /* set if register entry is invalid */
#define PFM_REG_RETFL_MASK	(PFM_REG_RETFL_NOTAVAIL|PFM_REG_RETFL_EINVAL)

#define PFM_REG_HAS_ERROR(flag)	(((flag) & PFM_REG_RETFL_MASK) != 0)

typedef unsigned char pfm_uuid_t[16];	/* custom sampling buffer identifier type */

/*
 * Request structure used to define a context
 */
typedef struct {
	pfm_uuid_t     ctx_smpl_buf_id;	 /* which buffer format to use (if needed) */
	unsigned long  ctx_flags;	 /* noblock/block */
	unsigned short ctx_nextra_sets;	 /* number of extra event sets (you always get 1) */
	unsigned short ctx_reserved1;	 /* for future use */
	int	       ctx_fd;		 /* return arg: unique identification for context */
	void	       *ctx_smpl_vaddr;	 /* return arg: virtual address of sampling buffer, is used */
	unsigned long  ctx_reserved2[11];/* for future use */
} pfarg_context_t;

/*
 * Request structure used to write/read a PMC or PMD
 */
typedef struct {
	unsigned int	reg_num;	   /* which register */
	unsigned short	reg_set;	   /* event set for this register */
	unsigned short	reg_reserved1;	   /* for future use */

	unsigned long	reg_value;	   /* initial pmc/pmd value */
	unsigned long	reg_flags;	   /* input: pmc/pmd flags, return: reg error */

	unsigned long	reg_long_reset;	   /* reset after buffer overflow notification */
	unsigned long	reg_short_reset;   /* reset after counter overflow */

	unsigned long	reg_reset_pmds[4]; /* which other counters to reset on overflow */
	unsigned long	reg_random_seed;   /* seed value when randomization is used */
	unsigned long	reg_random_mask;   /* bitmask used to limit random value */
	unsigned long   reg_last_reset_val;/* return: PMD last reset value */

	unsigned long	reg_smpl_pmds[4];  /* which pmds are accessed when PMC overflows */
	unsigned long	reg_smpl_eventid;  /* opaque sampling event identifier */

	unsigned long   reg_reserved2[3];   /* for future use */
} pfarg_reg_t;

typedef struct {
	unsigned int	dbreg_num;		/* which debug register */
	unsigned short	dbreg_set;		/* event set for this register */
	unsigned short	dbreg_reserved1;	/* for future use */
	unsigned long	dbreg_value;		/* value for debug register */
	unsigned long	dbreg_flags;		/* return: dbreg error */
	unsigned long	dbreg_reserved2[1];	/* for future use */
} pfarg_dbreg_t;

typedef struct {
	unsigned int	ft_version;	/* perfmon: major [16-31], minor [0-15] */
	unsigned int	ft_reserved;	/* reserved for future use */
	unsigned long	reserved[4];	/* for future use */
} pfarg_features_t;

typedef struct {
	pid_t		load_pid;	   /* process to load the context into */
	unsigned short	load_set;	   /* first event set to load */
	unsigned short	load_reserved1;	   /* for future use */
	unsigned long	load_reserved2[3]; /* for future use */
} pfarg_load_t;

typedef struct {
	int		msg_type;		/* generic message header */
	int		msg_ctx_fd;		/* generic message header */
	unsigned long	msg_ovfl_pmds[4];	/* which PMDs overflowed */
	unsigned short  msg_active_set;		/* active set at the time of overflow */
	unsigned short  msg_reserved1;		/* for future use */
	unsigned int    msg_reserved2;		/* for future use */
	unsigned long	msg_tstamp;		/* for perf tuning/debug */
} pfm_ovfl_msg_t;

typedef struct {
	int		msg_type;		/* generic message header */
	int		msg_ctx_fd;		/* generic message header */
	unsigned long	msg_tstamp;		/* for perf tuning */
} pfm_end_msg_t;

typedef struct {
	int		msg_type;		/* type of the message */
	int		msg_ctx_fd;		/* unique identifier for the context */
	unsigned long	msg_tstamp;		/* for perf tuning */
} pfm_gen_msg_t;

#define PFM_MSG_OVFL	1	/* an overflow happened */
#define PFM_MSG_END	2	/* task to which context was attached ended */

typedef union {
	pfm_ovfl_msg_t	pfm_ovfl_msg;
	pfm_end_msg_t	pfm_end_msg;
	pfm_gen_msg_t	pfm_gen_msg;
} pfm_msg_t;

/*
 * Define the version numbers for both perfmon as a whole and the sampling buffer format.
 */
#define PFM_VERSION_MAJ		 2U
#define PFM_VERSION_MIN		 0U
#define PFM_VERSION		 (((PFM_VERSION_MAJ&0xffff)<<16)|(PFM_VERSION_MIN & 0xffff))
#define PFM_VERSION_MAJOR(x)	 (((x)>>16) & 0xffff)
#define PFM_VERSION_MINOR(x)	 ((x) & 0xffff)


/*
 * miscellaneous architected definitions
 */
#define PMU_FIRST_COUNTER	4	/* first counting monitor (PMC/PMD) */
#define PMU_MAX_PMCS		256	/* maximum architected number of PMC registers */
#define PMU_MAX_PMDS		256	/* maximum architected number of PMD registers */

#ifdef __KERNEL__

extern long perfmonctl(int fd, int cmd, void *arg, int narg);

typedef struct {
	void (*handler)(int irq, void *arg, struct pt_regs *regs);
} pfm_intr_handler_desc_t;

extern void pfm_save_regs (struct task_struct *);
extern void pfm_load_regs (struct task_struct *);

extern void pfm_exit_thread(struct task_struct *);
extern int  pfm_use_debug_registers(struct task_struct *);
extern int  pfm_release_debug_registers(struct task_struct *);
extern void pfm_syst_wide_update_task(struct task_struct *, unsigned long info, int is_ctxswin);
extern void pfm_inherit(struct task_struct *task, struct pt_regs *regs);
extern void pfm_init_percpu(void);
extern void pfm_handle_work(void);
extern int  pfm_install_alt_pmu_interrupt(pfm_intr_handler_desc_t *h);
extern int  pfm_remove_alt_pmu_interrupt(pfm_intr_handler_desc_t *h);



/*
 * Reset PMD register flags
 */
#define PFM_PMD_SHORT_RESET	0
#define PFM_PMD_LONG_RESET	1

typedef union {
	unsigned int val;
	struct {
		unsigned int notify_user:1;	/* notify user program of overflow */
		unsigned int reset_ovfl_pmds:1;	/* reset overflowed PMDs */
		unsigned int block_task:1;	/* block monitored task on kernel exit */
		unsigned int mask_monitoring:1; /* mask monitors via PMCx.plm */
		unsigned int reserved:28;	/* for future use */
	} bits;
} pfm_ovfl_ctrl_t;

typedef struct {
	unsigned char	ovfl_pmd;			/* index of overflowed PMD  */
	unsigned char   ovfl_notify;			/* =1 if monitor requested overflow notification */
	unsigned short  active_set;			/* event set active at the time of the overflow */
	pfm_ovfl_ctrl_t ovfl_ctrl;			/* return: perfmon controls to set by handler */

	unsigned long   pmd_last_reset;			/* last reset value of of the PMD */
	unsigned long	smpl_pmds[4];			/* bitmask of other PMD of interest on overflow */
	unsigned long   smpl_pmds_values[PMU_MAX_PMDS]; /* values for the other PMDs of interest */
	unsigned long   pmd_value;			/* current 64-bit value of the PMD */
	unsigned long	pmd_eventid;			/* eventid associated with PMD */
} pfm_ovfl_arg_t;


typedef struct {
	char		*fmt_name;
	pfm_uuid_t	fmt_uuid;
	size_t		fmt_arg_size;
	unsigned long	fmt_flags;

	int		(*fmt_validate)(struct task_struct *task, unsigned int flags, int cpu, void *arg);
	int		(*fmt_getsize)(struct task_struct *task, unsigned int flags, int cpu, void *arg, unsigned long *size);
	int 		(*fmt_init)(struct task_struct *task, void *buf, unsigned int flags, int cpu, void *arg);
	int		(*fmt_handler)(struct task_struct *task, void *buf, pfm_ovfl_arg_t *arg, struct pt_regs *regs, unsigned long stamp);
	int		(*fmt_restart)(struct task_struct *task, pfm_ovfl_ctrl_t *ctrl, void *buf, struct pt_regs *regs);
	int		(*fmt_restart_active)(struct task_struct *task, pfm_ovfl_ctrl_t *ctrl, void *buf, struct pt_regs *regs);
	int		(*fmt_exit)(struct task_struct *task, void *buf, struct pt_regs *regs);

	struct list_head fmt_list;
} pfm_buffer_fmt_t;

extern int pfm_register_buffer_fmt(pfm_buffer_fmt_t *fmt);
extern int pfm_unregister_buffer_fmt(pfm_uuid_t uuid);

/*
 * perfmon interface exported to modules
 */
extern int pfm_mod_read_pmds(struct task_struct *, void *req, unsigned int nreq, struct pt_regs *regs);
extern int pfm_mod_write_pmcs(struct task_struct *, void *req, unsigned int nreq, struct pt_regs *regs);
extern int pfm_mod_write_ibrs(struct task_struct *task, void *req, unsigned int nreq, struct pt_regs *regs);
extern int pfm_mod_write_dbrs(struct task_struct *task, void *req, unsigned int nreq, struct pt_regs *regs);

/*
 * describe the content of the local_cpu_date->pfm_syst_info field
 */
#define PFM_CPUINFO_SYST_WIDE	0x1	/* if set a system wide session exists */
#define PFM_CPUINFO_DCR_PP	0x2	/* if set the system wide session has started */
#define PFM_CPUINFO_EXCL_IDLE	0x4	/* the system wide session excludes the idle task */

/*
 * sysctl control structure. visible to sampling formats
 */
typedef struct {
	int	debug;		/* turn on/off debugging via syslog */
	int	debug_ovfl;	/* turn on/off debug printk in overflow handler */
	int	fastctxsw;	/* turn on/off fast (unsecure) ctxsw */
	int	expert_mode;	/* turn on/off value checking */
} pfm_sysctl_t;
extern pfm_sysctl_t pfm_sysctl;


#endif /* __KERNEL__ */

#endif /* _ASM_IA64_PERFMON_H */
class='alt'>
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502





                                                                 
                                      





                                                                              

                         


                         


                        
                                  





                        
                        
                    












                                                        


                                        




















                                                                      

                                            





                                                    






                                                                             

                 













































                                                                                
                                   














                                                                                




                                                                             
                           


                                                                           










                                                           
                                                                        








                                                     














                                             
 







                                                                   


                                           






                                                                                
                         









                                                       

                                 







                                                                      


















































































                                                                               

                                      

                                                

                                          
                                     



                                            

  
                                                          

                                             

                             



                                          
                                                         
                                    
                                                             
                                        












































                                                              








                                                              
                                                                  
 
                                                         
 


                                                 
 




                                         

                                                          

                                         
                 





                                     

 
                                                           
 






                                                         

 
                                            










                                     
                                           












                                      




                                             


                                              
                                                 

                                  

                                            





















                                                                  

                                   

                                                  





                        
/*
 * linux/drivers/video/hitfb.c -- Hitachi LCD frame buffer device
 *
 * (C) 1999 Mihai Spatar
 * (C) 2000 YAEGASHI Takeshi
 * (C) 2003, 2004 Paul Mundt
 * (C) 2003, 2004, 2006 Andriy Skulysh
 *
 *  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.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>

#include <asm/machvec.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/io.h>
#include <asm/hd64461.h>
#include <cpu/dac.h>

#define	WIDTH 640

static struct fb_var_screeninfo hitfb_var __initdata = {
	.activate	= FB_ACTIVATE_NOW,
	.height		= -1,
	.width		= -1,
	.vmode		= FB_VMODE_NONINTERLACED,
};

static struct fb_fix_screeninfo hitfb_fix __initdata = {
	.id		= "Hitachi HD64461",
	.type		= FB_TYPE_PACKED_PIXELS,
	.accel		= FB_ACCEL_NONE,
};

static inline void hitfb_accel_wait(void)
{
	while (fb_readw(HD64461_GRCFGR) & HD64461_GRCFGR_ACCSTATUS) ;
}

static inline void hitfb_accel_start(int truecolor)
{
	if (truecolor) {
		fb_writew(6, HD64461_GRCFGR);
	} else {
		fb_writew(7, HD64461_GRCFGR);
	}
}

static inline void hitfb_accel_set_dest(int truecolor, u16 dx, u16 dy,
					u16 width, u16 height)
{
	u32 saddr = WIDTH * dy + dx;
	if (truecolor)
		saddr <<= 1;

	fb_writew(width-1, HD64461_BBTDWR);
	fb_writew(height-1, HD64461_BBTDHR);

	fb_writew(saddr & 0xffff, HD64461_BBTDSARL);
	fb_writew(saddr >> 16, HD64461_BBTDSARH);

}

static inline void hitfb_accel_bitblt(int truecolor, u16 sx, u16 sy, u16 dx,
				      u16 dy, u16 width, u16 height, u16 rop,
				      u32 mask_addr)
{
	u32 saddr, daddr;
	u32 maddr = 0;

	height--;
	width--;
	fb_writew(rop, HD64461_BBTROPR);
	if ((sy < dy) || ((sy == dy) && (sx <= dx))) {
		saddr = WIDTH * (sy + height) + sx + width;
		daddr = WIDTH * (dy + height) + dx + width;
		if (mask_addr) {
			if (truecolor)
				maddr = ((width >> 3) + 1) * (height + 1) - 1;
			else
				maddr =
				    (((width >> 4) + 1) * (height + 1) - 1) * 2;

			fb_writew((1 << 5) | 1, HD64461_BBTMDR);
		} else
			fb_writew(1, HD64461_BBTMDR);
	} else {
		saddr = WIDTH * sy + sx;
		daddr = WIDTH * dy + dx;
		if (mask_addr) {
			fb_writew((1 << 5), HD64461_BBTMDR);
		} else {
			fb_writew(0, HD64461_BBTMDR);
		}
	}
	if (truecolor) {
		saddr <<= 1;
		daddr <<= 1;
	}
	fb_writew(width, HD64461_BBTDWR);
	fb_writew(height, HD64461_BBTDHR);
	fb_writew(saddr & 0xffff, HD64461_BBTSSARL);
	fb_writew(saddr >> 16, HD64461_BBTSSARH);
	fb_writew(daddr & 0xffff, HD64461_BBTDSARL);
	fb_writew(daddr >> 16, HD64461_BBTDSARH);
	if (mask_addr) {
		maddr += mask_addr;
		fb_writew(maddr & 0xffff, HD64461_BBTMARL);
		fb_writew(maddr >> 16, HD64461_BBTMARH);
	}
	hitfb_accel_start(truecolor);
}

static void hitfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
{
	if (rect->rop != ROP_COPY)
		cfb_fillrect(p, rect);
	else {
		hitfb_accel_wait();
		fb_writew(0x00f0, HD64461_BBTROPR);
		fb_writew(16, HD64461_BBTMDR);

		if (p->var.bits_per_pixel == 16) {
			fb_writew(((u32 *) (p->pseudo_palette))[rect->color],
				  HD64461_GRSCR);
			hitfb_accel_set_dest(1, rect->dx, rect->dy, rect->width,
					     rect->height);
			hitfb_accel_start(1);
		} else {
			fb_writew(rect->color, HD64461_GRSCR);
			hitfb_accel_set_dest(0, rect->dx, rect->dy, rect->width,
					     rect->height);
			hitfb_accel_start(0);
		}
	}
}

static void hitfb_copyarea(struct fb_info *p, const struct fb_copyarea *area)
{
	hitfb_accel_wait();
	hitfb_accel_bitblt(p->var.bits_per_pixel == 16, area->sx, area->sy,
			   area->dx, area->dy, area->width, area->height,
			   0x00cc, 0);
}

static int hitfb_pan_display(struct fb_var_screeninfo *var,
			     struct fb_info *info)
{
	int xoffset = var->xoffset;
	int yoffset = var->yoffset;

	if (xoffset != 0)
		return -EINVAL;

	fb_writew((yoffset*info->fix.line_length)>>10, HD64461_LCDCBAR);

	return 0;
}

int hitfb_blank(int blank_mode, struct fb_info *info)
{
	unsigned short v;

	if (blank_mode) {
		v = fb_readw(HD64461_LDR1);
		v &= ~HD64461_LDR1_DON;
		fb_writew(v, HD64461_LDR1);

		v = fb_readw(HD64461_LCDCCR);
		v |= HD64461_LCDCCR_MOFF;
		fb_writew(v, HD64461_LCDCCR);

		v = fb_readw(HD64461_STBCR);
		v |= HD64461_STBCR_SLCDST;
		fb_writew(v, HD64461_STBCR);
	} else {
		v = fb_readw(HD64461_STBCR);
		v &= ~HD64461_STBCR_SLCDST;
		fb_writew(v, HD64461_STBCR);

		v = fb_readw(HD64461_LCDCCR);
		v &= ~(HD64461_LCDCCR_MOFF | HD64461_LCDCCR_STREQ);
		fb_writew(v, HD64461_LCDCCR);

		do {
		    v = fb_readw(HD64461_LCDCCR);
		} while(v&HD64461_LCDCCR_STBACK);

		v = fb_readw(HD64461_LDR1);
		v |= HD64461_LDR1_DON;
		fb_writew(v, HD64461_LDR1);
	}
	return 0;
}

static int hitfb_setcolreg(unsigned regno, unsigned red, unsigned green,
			   unsigned blue, unsigned transp, struct fb_info *info)
{
	if (regno >= 256)
		return 1;

	switch (info->var.bits_per_pixel) {
	case 8:
		fb_writew(regno << 8, HD64461_CPTWAR);
		fb_writew(red >> 10, HD64461_CPTWDR);
		fb_writew(green >> 10, HD64461_CPTWDR);
		fb_writew(blue >> 10, HD64461_CPTWDR);
		break;
	case 16:
		if (regno >= 16)
			return 1;
		((u32 *) (info->pseudo_palette))[regno] =
		    ((red & 0xf800)) |
		    ((green & 0xfc00) >> 5) | ((blue & 0xf800) >> 11);
		break;
	}
	return 0;
}

static int hitfb_sync(struct fb_info *info)
{
	hitfb_accel_wait();

	return 0;
}

static int hitfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
	int maxy;

	var->xres = info->var.xres;
	var->xres_virtual = info->var.xres;
	var->yres = info->var.yres;

	if ((var->bits_per_pixel != 8) && (var->bits_per_pixel != 16))
		var->bits_per_pixel = info->var.bits_per_pixel;

	if (var->yres_virtual < var->yres)
		var->yres_virtual = var->yres;

	maxy = info->fix.smem_len / var->xres;

	if (var->bits_per_pixel == 16)
		maxy /= 2;

	if (var->yres_virtual > maxy)
		var->yres_virtual = maxy;

	var->xoffset = 0;
	var->yoffset = 0;

	switch (var->bits_per_pixel) {
	case 8:
		var->red.offset = 0;
		var->red.length = 8;
		var->green.offset = 0;
		var->green.length = 8;
		var->blue.offset = 0;
		var->blue.length = 8;
		var->transp.offset = 0;
		var->transp.length = 0;
		break;
	case 16:		/* RGB 565 */
		var->red.offset = 11;
		var->red.length = 5;
		var->green.offset = 5;
		var->green.length = 6;
		var->blue.offset = 0;
		var->blue.length = 5;
		var->transp.offset = 0;
		var->transp.length = 0;
		break;
	}

	return 0;
}

static int hitfb_set_par(struct fb_info *info)
{
	unsigned short ldr3;

	switch (info->var.bits_per_pixel) {
	case 8:
		info->fix.line_length = info->var.xres;
		info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
		info->fix.ypanstep = 16;
		break;
	case 16:
		info->fix.line_length = info->var.xres*2;
		info->fix.visual = FB_VISUAL_TRUECOLOR;
		info->fix.ypanstep = 8;
		break;
	}

	fb_writew(info->fix.line_length, HD64461_LCDCLOR);
	ldr3 = fb_readw(HD64461_LDR3);
	ldr3 &= ~15;
	ldr3 |= (info->var.bits_per_pixel == 8) ? 4 : 8;
	fb_writew(ldr3, HD64461_LDR3);
	return 0;
}

static struct fb_ops hitfb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= hitfb_check_var,
	.fb_set_par		= hitfb_set_par,
	.fb_setcolreg	= hitfb_setcolreg,
	.fb_blank	= hitfb_blank,
	.fb_sync	= hitfb_sync,
	.fb_pan_display = hitfb_pan_display,
	.fb_fillrect	= hitfb_fillrect,
	.fb_copyarea	= hitfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};

static int __init hitfb_probe(struct platform_device *dev)
{
	unsigned short lcdclor, ldr3, ldvndr;
	struct fb_info *info;
	int ret;

	if (fb_get_options("hitfb", NULL))
		return -ENODEV;

	hitfb_fix.mmio_start = HD64461_IO_OFFSET(0x1000);
	hitfb_fix.mmio_len = 0x1000;
	hitfb_fix.smem_start = HD64461_IO_OFFSET(0x02000000);
	hitfb_fix.smem_len = 512 * 1024;

	lcdclor = fb_readw(HD64461_LCDCLOR);
	ldvndr = fb_readw(HD64461_LDVNDR);
	ldr3 = fb_readw(HD64461_LDR3);

	switch (ldr3 & 15) {
	default:
	case 4:
		hitfb_var.bits_per_pixel = 8;
		hitfb_var.xres = lcdclor;
		break;
	case 8:
		hitfb_var.bits_per_pixel = 16;
		hitfb_var.xres = lcdclor / 2;
		break;
	}
	hitfb_fix.line_length = lcdclor;
	hitfb_fix.visual = (hitfb_var.bits_per_pixel == 8) ?
	    FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
	hitfb_var.yres = ldvndr + 1;
	hitfb_var.xres_virtual = hitfb_var.xres;
	hitfb_var.yres_virtual = hitfb_fix.smem_len / lcdclor;
	switch (hitfb_var.bits_per_pixel) {
	case 8:
		hitfb_var.red.offset = 0;
		hitfb_var.red.length = 8;
		hitfb_var.green.offset = 0;
		hitfb_var.green.length = 8;
		hitfb_var.blue.offset = 0;
		hitfb_var.blue.length = 8;
		hitfb_var.transp.offset = 0;
		hitfb_var.transp.length = 0;
		break;
	case 16:		/* RGB 565 */
		hitfb_var.red.offset = 11;
		hitfb_var.red.length = 5;
		hitfb_var.green.offset = 5;
		hitfb_var.green.length = 6;
		hitfb_var.blue.offset = 0;
		hitfb_var.blue.length = 5;
		hitfb_var.transp.offset = 0;
		hitfb_var.transp.length = 0;
		break;
	}

	info = framebuffer_alloc(sizeof(u32) * 16, &dev->dev);
	if (unlikely(!info))
		return -ENOMEM;

	info->fbops = &hitfb_ops;
	info->var = hitfb_var;
	info->fix = hitfb_fix;
	info->pseudo_palette = info->par;
	info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN |
		FBINFO_HWACCEL_FILLRECT | FBINFO_HWACCEL_COPYAREA;

	info->screen_base = (void *)hitfb_fix.smem_start;

	ret = fb_alloc_cmap(&info->cmap, 256, 0);
	if (unlikely(ret < 0))
		goto err_fb;

	ret = register_framebuffer(info);
	if (unlikely(ret < 0))
		goto err;

	platform_set_drvdata(dev, info);

	printk(KERN_INFO "fb%d: %s frame buffer device\n",
	       info->node, info->fix.id);

	return 0;

err:
	fb_dealloc_cmap(&info->cmap);
err_fb:
	framebuffer_release(info);
	return ret;
}

static int __exit hitfb_remove(struct platform_device *dev)
{
	struct fb_info *info = platform_get_drvdata(dev);

	unregister_framebuffer(info);
	fb_dealloc_cmap(&info->cmap);
	framebuffer_release(info);

	return 0;
}

static int hitfb_suspend(struct device *dev)
{
	u16 v;

	hitfb_blank(1,0);
	v = fb_readw(HD64461_STBCR);
	v |= HD64461_STBCR_SLCKE_IST;
	fb_writew(v, HD64461_STBCR);

	return 0;
}

static int hitfb_resume(struct device *dev)
{
	u16 v;

	v = fb_readw(HD64461_STBCR);
	v &= ~HD64461_STBCR_SLCKE_OST;
	msleep(100);
	v = fb_readw(HD64461_STBCR);
	v &= ~HD64461_STBCR_SLCKE_IST;
	fb_writew(v, HD64461_STBCR);
	hitfb_blank(0,0);

	return 0;
}

static struct dev_pm_ops hitfb_dev_pm_ops = {
	.suspend	= hitfb_suspend,
	.resume		= hitfb_resume,
};

static struct platform_driver hitfb_driver = {
	.probe		= hitfb_probe,
	.remove		= __exit_p(hitfb_remove),
	.driver		= {
		.name	= "hitfb",
		.owner	= THIS_MODULE,
		.pm	= &hitfb_dev_pm_ops,
	},
};

static struct platform_device hitfb_device = {
	.name	= "hitfb",
	.id	= -1,
};

static int __init hitfb_init(void)
{
	int ret;

	ret = platform_driver_register(&hitfb_driver);
	if (!ret) {
		ret = platform_device_register(&hitfb_device);
		if (ret)
			platform_driver_unregister(&hitfb_driver);
	}
	return ret;
}


static void __exit hitfb_exit(void)
{
	platform_device_unregister(&hitfb_device);
	platform_driver_unregister(&hitfb_driver);
}

module_init(hitfb_init);
module_exit(hitfb_exit);

MODULE_LICENSE("GPL");