| Imre Deak | 569755c | 2007-07-17 04:05:56 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * OMAP1 internal LCD controller | 
 | 3 |  * | 
 | 4 |  * Copyright (C) 2004 Nokia Corporation | 
 | 5 |  * Author: Imre Deak <imre.deak@nokia.com> | 
 | 6 |  * | 
 | 7 |  * This program is free software; you can redistribute it and/or modify it | 
 | 8 |  * under the terms of the GNU General Public License as published by the | 
 | 9 |  * Free Software Foundation; either version 2 of the License, or (at your | 
 | 10 |  * option) any later version. | 
 | 11 |  * | 
 | 12 |  * This program is distributed in the hope that it will be useful, but | 
 | 13 |  * WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 14 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 | 15 |  * General Public License for more details. | 
 | 16 |  * | 
 | 17 |  * You should have received a copy of the GNU General Public License along | 
 | 18 |  * with this program; if not, write to the Free Software Foundation, Inc., | 
 | 19 |  * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. | 
 | 20 |  */ | 
 | 21 | #include <linux/module.h> | 
 | 22 | #include <linux/device.h> | 
 | 23 | #include <linux/interrupt.h> | 
 | 24 | #include <linux/spinlock.h> | 
 | 25 | #include <linux/err.h> | 
 | 26 | #include <linux/mm.h> | 
 | 27 | #include <linux/fb.h> | 
 | 28 | #include <linux/dma-mapping.h> | 
 | 29 | #include <linux/vmalloc.h> | 
 | 30 | #include <linux/clk.h> | 
 | 31 |  | 
| Russell King | a09e64f | 2008-08-05 16:14:15 +0100 | [diff] [blame] | 32 | #include <mach/dma.h> | 
 | 33 | #include <mach/omapfb.h> | 
| Imre Deak | 569755c | 2007-07-17 04:05:56 -0700 | [diff] [blame] | 34 |  | 
 | 35 | #include <asm/mach-types.h> | 
 | 36 |  | 
 | 37 | #define MODULE_NAME			"lcdc" | 
 | 38 |  | 
 | 39 | #define OMAP_LCDC_BASE			0xfffec000 | 
 | 40 | #define OMAP_LCDC_SIZE			256 | 
 | 41 | #define OMAP_LCDC_IRQ			INT_LCD_CTRL | 
 | 42 |  | 
 | 43 | #define OMAP_LCDC_CONTROL		(OMAP_LCDC_BASE + 0x00) | 
 | 44 | #define OMAP_LCDC_TIMING0		(OMAP_LCDC_BASE + 0x04) | 
 | 45 | #define OMAP_LCDC_TIMING1		(OMAP_LCDC_BASE + 0x08) | 
 | 46 | #define OMAP_LCDC_TIMING2		(OMAP_LCDC_BASE + 0x0c) | 
 | 47 | #define OMAP_LCDC_STATUS		(OMAP_LCDC_BASE + 0x10) | 
 | 48 | #define OMAP_LCDC_SUBPANEL		(OMAP_LCDC_BASE + 0x14) | 
 | 49 | #define OMAP_LCDC_LINE_INT		(OMAP_LCDC_BASE + 0x18) | 
 | 50 | #define OMAP_LCDC_DISPLAY_STATUS	(OMAP_LCDC_BASE + 0x1c) | 
 | 51 |  | 
 | 52 | #define OMAP_LCDC_STAT_DONE		(1 << 0) | 
 | 53 | #define OMAP_LCDC_STAT_VSYNC		(1 << 1) | 
 | 54 | #define OMAP_LCDC_STAT_SYNC_LOST	(1 << 2) | 
 | 55 | #define OMAP_LCDC_STAT_ABC		(1 << 3) | 
 | 56 | #define OMAP_LCDC_STAT_LINE_INT		(1 << 4) | 
 | 57 | #define OMAP_LCDC_STAT_FUF		(1 << 5) | 
 | 58 | #define OMAP_LCDC_STAT_LOADED_PALETTE	(1 << 6) | 
 | 59 |  | 
 | 60 | #define OMAP_LCDC_CTRL_LCD_EN		(1 << 0) | 
 | 61 | #define OMAP_LCDC_CTRL_LCD_TFT		(1 << 7) | 
 | 62 | #define OMAP_LCDC_CTRL_LINE_IRQ_CLR_SEL	(1 << 10) | 
 | 63 |  | 
 | 64 | #define OMAP_LCDC_IRQ_VSYNC		(1 << 2) | 
 | 65 | #define OMAP_LCDC_IRQ_DONE		(1 << 3) | 
 | 66 | #define OMAP_LCDC_IRQ_LOADED_PALETTE	(1 << 4) | 
 | 67 | #define OMAP_LCDC_IRQ_LINE_NIRQ		(1 << 5) | 
 | 68 | #define OMAP_LCDC_IRQ_LINE		(1 << 6) | 
 | 69 | #define OMAP_LCDC_IRQ_MASK		(((1 << 5) - 1) << 2) | 
 | 70 |  | 
 | 71 | #define MAX_PALETTE_SIZE		PAGE_SIZE | 
 | 72 |  | 
 | 73 | enum lcdc_load_mode { | 
 | 74 | 	OMAP_LCDC_LOAD_PALETTE, | 
 | 75 | 	OMAP_LCDC_LOAD_FRAME, | 
 | 76 | 	OMAP_LCDC_LOAD_PALETTE_AND_FRAME | 
 | 77 | }; | 
 | 78 |  | 
 | 79 | static struct omap_lcd_controller { | 
 | 80 | 	enum omapfb_update_mode	update_mode; | 
 | 81 | 	int			ext_mode; | 
 | 82 |  | 
 | 83 | 	unsigned long		frame_offset; | 
 | 84 | 	int			screen_width; | 
 | 85 | 	int			xres; | 
 | 86 | 	int			yres; | 
 | 87 |  | 
 | 88 | 	enum omapfb_color_format	color_mode; | 
 | 89 | 	int			bpp; | 
 | 90 | 	void			*palette_virt; | 
 | 91 | 	dma_addr_t		palette_phys; | 
 | 92 | 	int			palette_code; | 
 | 93 | 	int			palette_size; | 
 | 94 |  | 
 | 95 | 	unsigned int		irq_mask; | 
 | 96 | 	struct completion	last_frame_complete; | 
 | 97 | 	struct completion	palette_load_complete; | 
 | 98 | 	struct clk		*lcd_ck; | 
 | 99 | 	struct omapfb_device	*fbdev; | 
 | 100 |  | 
 | 101 | 	void			(*dma_callback)(void *data); | 
 | 102 | 	void			*dma_callback_data; | 
 | 103 |  | 
 | 104 | 	int			fbmem_allocated; | 
 | 105 | 	dma_addr_t		vram_phys; | 
 | 106 | 	void			*vram_virt; | 
 | 107 | 	unsigned long		vram_size; | 
 | 108 | } lcdc; | 
 | 109 |  | 
 | 110 | static void inline enable_irqs(int mask) | 
 | 111 | { | 
 | 112 | 	lcdc.irq_mask |= mask; | 
 | 113 | } | 
 | 114 |  | 
 | 115 | static void inline disable_irqs(int mask) | 
 | 116 | { | 
 | 117 | 	lcdc.irq_mask &= ~mask; | 
 | 118 | } | 
 | 119 |  | 
 | 120 | static void set_load_mode(enum lcdc_load_mode mode) | 
 | 121 | { | 
 | 122 | 	u32 l; | 
 | 123 |  | 
 | 124 | 	l = omap_readl(OMAP_LCDC_CONTROL); | 
 | 125 | 	l &= ~(3 << 20); | 
 | 126 | 	switch (mode) { | 
 | 127 | 	case OMAP_LCDC_LOAD_PALETTE: | 
 | 128 | 		l |= 1 << 20; | 
 | 129 | 		break; | 
 | 130 | 	case OMAP_LCDC_LOAD_FRAME: | 
 | 131 | 		l |= 2 << 20; | 
 | 132 | 		break; | 
 | 133 | 	case OMAP_LCDC_LOAD_PALETTE_AND_FRAME: | 
 | 134 | 		break; | 
 | 135 | 	default: | 
 | 136 | 		BUG(); | 
 | 137 | 	} | 
 | 138 | 	omap_writel(l, OMAP_LCDC_CONTROL); | 
 | 139 | } | 
 | 140 |  | 
 | 141 | static void enable_controller(void) | 
 | 142 | { | 
 | 143 | 	u32 l; | 
 | 144 |  | 
 | 145 | 	l = omap_readl(OMAP_LCDC_CONTROL); | 
 | 146 | 	l |= OMAP_LCDC_CTRL_LCD_EN; | 
 | 147 | 	l &= ~OMAP_LCDC_IRQ_MASK; | 
 | 148 | 	l |= lcdc.irq_mask | OMAP_LCDC_IRQ_DONE;	/* enabled IRQs */ | 
 | 149 | 	omap_writel(l, OMAP_LCDC_CONTROL); | 
 | 150 | } | 
 | 151 |  | 
 | 152 | static void disable_controller_async(void) | 
 | 153 | { | 
 | 154 | 	u32 l; | 
 | 155 | 	u32 mask; | 
 | 156 |  | 
 | 157 | 	l = omap_readl(OMAP_LCDC_CONTROL); | 
 | 158 | 	mask = OMAP_LCDC_CTRL_LCD_EN | OMAP_LCDC_IRQ_MASK; | 
 | 159 | 	/* | 
 | 160 | 	 * Preserve the DONE mask, since we still want to get the | 
 | 161 | 	 * final DONE irq. It will be disabled in the IRQ handler. | 
 | 162 | 	 */ | 
 | 163 | 	mask &= ~OMAP_LCDC_IRQ_DONE; | 
 | 164 | 	l &= ~mask; | 
 | 165 | 	omap_writel(l, OMAP_LCDC_CONTROL); | 
 | 166 | } | 
 | 167 |  | 
 | 168 | static void disable_controller(void) | 
 | 169 | { | 
 | 170 | 	init_completion(&lcdc.last_frame_complete); | 
 | 171 | 	disable_controller_async(); | 
 | 172 | 	if (!wait_for_completion_timeout(&lcdc.last_frame_complete, | 
 | 173 | 				msecs_to_jiffies(500))) | 
 | 174 | 		dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n"); | 
 | 175 | } | 
 | 176 |  | 
 | 177 | static void reset_controller(u32 status) | 
 | 178 | { | 
 | 179 | 	static unsigned long reset_count; | 
 | 180 | 	static unsigned long last_jiffies; | 
 | 181 |  | 
 | 182 | 	disable_controller_async(); | 
 | 183 | 	reset_count++; | 
 | 184 | 	if (reset_count == 1 || time_after(jiffies, last_jiffies + HZ)) { | 
 | 185 | 		dev_err(lcdc.fbdev->dev, | 
 | 186 | 			  "resetting (status %#010x,reset count %lu)\n", | 
 | 187 | 			  status, reset_count); | 
 | 188 | 		last_jiffies = jiffies; | 
 | 189 | 	} | 
 | 190 | 	if (reset_count < 100) { | 
 | 191 | 		enable_controller(); | 
 | 192 | 	} else { | 
 | 193 | 		reset_count = 0; | 
 | 194 | 		dev_err(lcdc.fbdev->dev, | 
 | 195 | 			"too many reset attempts, giving up.\n"); | 
 | 196 | 	} | 
 | 197 | } | 
 | 198 |  | 
 | 199 | /* | 
 | 200 |  * Configure the LCD DMA according to the current mode specified by parameters | 
 | 201 |  * in lcdc.fbdev and fbdev->var. | 
 | 202 |  */ | 
 | 203 | static void setup_lcd_dma(void) | 
 | 204 | { | 
 | 205 | 	static const int dma_elem_type[] = { | 
 | 206 | 		0, | 
 | 207 | 		OMAP_DMA_DATA_TYPE_S8, | 
 | 208 | 		OMAP_DMA_DATA_TYPE_S16, | 
 | 209 | 		0, | 
 | 210 | 		OMAP_DMA_DATA_TYPE_S32, | 
 | 211 | 	}; | 
 | 212 | 	struct omapfb_plane_struct *plane = lcdc.fbdev->fb_info[0]->par; | 
 | 213 | 	struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var; | 
 | 214 | 	unsigned long	src; | 
 | 215 | 	int		esize, xelem, yelem; | 
 | 216 |  | 
 | 217 | 	src = lcdc.vram_phys + lcdc.frame_offset; | 
 | 218 |  | 
 | 219 | 	switch (var->rotate) { | 
 | 220 | 	case 0: | 
 | 221 | 		if (plane->info.mirror || (src & 3) || | 
 | 222 | 		    lcdc.color_mode == OMAPFB_COLOR_YUV420 || | 
 | 223 | 		    (lcdc.xres & 1)) | 
 | 224 | 			esize = 2; | 
 | 225 | 		else | 
 | 226 | 			esize = 4; | 
 | 227 | 		xelem = lcdc.xres * lcdc.bpp / 8 / esize; | 
 | 228 | 		yelem = lcdc.yres; | 
 | 229 | 		break; | 
 | 230 | 	case 90: | 
 | 231 | 	case 180: | 
 | 232 | 	case 270: | 
 | 233 | 		if (cpu_is_omap15xx()) { | 
 | 234 | 			BUG(); | 
 | 235 | 		} | 
 | 236 | 		esize = 2; | 
 | 237 | 		xelem = lcdc.yres * lcdc.bpp / 16; | 
 | 238 | 		yelem = lcdc.xres; | 
 | 239 | 		break; | 
 | 240 | 	default: | 
 | 241 | 		BUG(); | 
 | 242 | 		return; | 
 | 243 | 	} | 
 | 244 | #ifdef VERBOSE | 
 | 245 | 	dev_dbg(lcdc.fbdev->dev, | 
 | 246 | 		 "setup_dma: src %#010lx esize %d xelem %d yelem %d\n", | 
 | 247 | 		 src, esize, xelem, yelem); | 
 | 248 | #endif | 
 | 249 | 	omap_set_lcd_dma_b1(src, xelem, yelem, dma_elem_type[esize]); | 
 | 250 | 	if (!cpu_is_omap15xx()) { | 
 | 251 | 		int bpp = lcdc.bpp; | 
 | 252 |  | 
 | 253 | 		/* | 
 | 254 | 		 * YUV support is only for external mode when we have the | 
 | 255 | 		 * YUV window embedded in a 16bpp frame buffer. | 
 | 256 | 		 */ | 
 | 257 | 		if (lcdc.color_mode == OMAPFB_COLOR_YUV420) | 
 | 258 | 			bpp = 16; | 
 | 259 | 		/* Set virtual xres elem size */ | 
 | 260 | 		omap_set_lcd_dma_b1_vxres( | 
 | 261 | 			lcdc.screen_width * bpp / 8 / esize); | 
 | 262 | 		/* Setup transformations */ | 
 | 263 | 		omap_set_lcd_dma_b1_rotation(var->rotate); | 
 | 264 | 		omap_set_lcd_dma_b1_mirror(plane->info.mirror); | 
 | 265 | 	} | 
 | 266 | 	omap_setup_lcd_dma(); | 
 | 267 | } | 
 | 268 |  | 
 | 269 | static irqreturn_t lcdc_irq_handler(int irq, void *dev_id) | 
 | 270 | { | 
 | 271 | 	u32 status; | 
 | 272 |  | 
 | 273 | 	status = omap_readl(OMAP_LCDC_STATUS); | 
 | 274 |  | 
 | 275 | 	if (status & (OMAP_LCDC_STAT_FUF | OMAP_LCDC_STAT_SYNC_LOST)) | 
 | 276 | 		reset_controller(status); | 
 | 277 | 	else { | 
 | 278 | 		if (status & OMAP_LCDC_STAT_DONE) { | 
 | 279 | 			u32 l; | 
 | 280 |  | 
 | 281 | 			/* | 
 | 282 | 			 * Disable IRQ_DONE. The status bit will be cleared | 
 | 283 | 			 * only when the controller is reenabled and we don't | 
 | 284 | 			 * want to get more interrupts. | 
 | 285 | 			 */ | 
 | 286 | 			l = omap_readl(OMAP_LCDC_CONTROL); | 
 | 287 | 			l &= ~OMAP_LCDC_IRQ_DONE; | 
 | 288 | 			omap_writel(l, OMAP_LCDC_CONTROL); | 
 | 289 | 			complete(&lcdc.last_frame_complete); | 
 | 290 | 		} | 
 | 291 | 		if (status & OMAP_LCDC_STAT_LOADED_PALETTE) { | 
 | 292 | 			disable_controller_async(); | 
 | 293 | 			complete(&lcdc.palette_load_complete); | 
 | 294 | 		} | 
 | 295 | 	} | 
 | 296 |  | 
 | 297 | 	/* | 
 | 298 | 	 * Clear these interrupt status bits. | 
 | 299 | 	 * Sync_lost, FUF bits were cleared by disabling the LCD controller | 
 | 300 | 	 * LOADED_PALETTE can be cleared this way only in palette only | 
 | 301 | 	 * load mode. In other load modes it's cleared by disabling the | 
 | 302 | 	 * controller. | 
 | 303 | 	 */ | 
 | 304 | 	status &= ~(OMAP_LCDC_STAT_VSYNC | | 
 | 305 | 		    OMAP_LCDC_STAT_LOADED_PALETTE | | 
 | 306 | 		    OMAP_LCDC_STAT_ABC | | 
 | 307 | 		    OMAP_LCDC_STAT_LINE_INT); | 
 | 308 | 	omap_writel(status, OMAP_LCDC_STATUS); | 
 | 309 | 	return IRQ_HANDLED; | 
 | 310 | } | 
 | 311 |  | 
 | 312 | /* | 
 | 313 |  * Change to a new video mode. We defer this to a later time to avoid any | 
 | 314 |  * flicker and not to mess up the current LCD DMA context. For this we disable | 
| Joe Perches | 44363f1 | 2008-02-03 17:31:49 +0200 | [diff] [blame] | 315 |  * the LCD controller, which will generate a DONE irq after the last frame has | 
| Imre Deak | 569755c | 2007-07-17 04:05:56 -0700 | [diff] [blame] | 316 |  * been transferred. Then it'll be safe to reconfigure both the LCD controller | 
 | 317 |  * as well as the LCD DMA. | 
 | 318 |  */ | 
 | 319 | static int omap_lcdc_setup_plane(int plane, int channel_out, | 
 | 320 | 				 unsigned long offset, int screen_width, | 
 | 321 | 				 int pos_x, int pos_y, int width, int height, | 
 | 322 | 				 int color_mode) | 
 | 323 | { | 
 | 324 | 	struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var; | 
 | 325 | 	struct lcd_panel *panel = lcdc.fbdev->panel; | 
 | 326 | 	int rot_x, rot_y; | 
 | 327 |  | 
 | 328 | 	if (var->rotate == 0) { | 
 | 329 | 		rot_x = panel->x_res; | 
 | 330 | 		rot_y = panel->y_res; | 
 | 331 | 	} else { | 
 | 332 | 		rot_x = panel->y_res; | 
 | 333 | 		rot_y = panel->x_res; | 
 | 334 | 	} | 
 | 335 | 	if (plane != 0 || channel_out != 0 || pos_x != 0 || pos_y != 0 || | 
 | 336 | 	    width > rot_x || height > rot_y) { | 
 | 337 | #ifdef VERBOSE | 
 | 338 | 		dev_dbg(lcdc.fbdev->dev, | 
 | 339 | 			"invalid plane params plane %d pos_x %d pos_y %d " | 
 | 340 | 			"w %d h %d\n", plane, pos_x, pos_y, width, height); | 
 | 341 | #endif | 
 | 342 | 		return -EINVAL; | 
 | 343 | 	} | 
 | 344 |  | 
 | 345 | 	lcdc.frame_offset = offset; | 
 | 346 | 	lcdc.xres = width; | 
 | 347 | 	lcdc.yres = height; | 
 | 348 | 	lcdc.screen_width = screen_width; | 
 | 349 | 	lcdc.color_mode = color_mode; | 
 | 350 |  | 
 | 351 | 	switch (color_mode) { | 
 | 352 | 	case OMAPFB_COLOR_CLUT_8BPP: | 
 | 353 | 		lcdc.bpp = 8; | 
 | 354 | 		lcdc.palette_code = 0x3000; | 
 | 355 | 		lcdc.palette_size = 512; | 
 | 356 | 		break; | 
 | 357 | 	case OMAPFB_COLOR_RGB565: | 
 | 358 | 		lcdc.bpp = 16; | 
 | 359 | 		lcdc.palette_code = 0x4000; | 
 | 360 | 		lcdc.palette_size = 32; | 
 | 361 | 		break; | 
 | 362 | 	case OMAPFB_COLOR_RGB444: | 
 | 363 | 		lcdc.bpp = 16; | 
 | 364 | 		lcdc.palette_code = 0x4000; | 
 | 365 | 		lcdc.palette_size = 32; | 
 | 366 | 		break; | 
 | 367 | 	case OMAPFB_COLOR_YUV420: | 
 | 368 | 		if (lcdc.ext_mode) { | 
 | 369 | 			lcdc.bpp = 12; | 
 | 370 | 			break; | 
 | 371 | 		} | 
 | 372 | 		/* fallthrough */ | 
 | 373 | 	case OMAPFB_COLOR_YUV422: | 
 | 374 | 		if (lcdc.ext_mode) { | 
 | 375 | 			lcdc.bpp = 16; | 
 | 376 | 			break; | 
 | 377 | 		} | 
 | 378 | 		/* fallthrough */ | 
 | 379 | 	default: | 
 | 380 | 		/* FIXME: other BPPs. | 
 | 381 | 		 * bpp1: code  0,     size 256 | 
 | 382 | 		 * bpp2: code  0x1000 size 256 | 
 | 383 | 		 * bpp4: code  0x2000 size 256 | 
 | 384 | 		 * bpp12: code 0x4000 size 32 | 
 | 385 | 		 */ | 
 | 386 | 		dev_dbg(lcdc.fbdev->dev, "invalid color mode %d\n", color_mode); | 
 | 387 | 		BUG(); | 
 | 388 | 		return -1; | 
 | 389 | 	} | 
 | 390 |  | 
 | 391 | 	if (lcdc.ext_mode) { | 
 | 392 | 		setup_lcd_dma(); | 
 | 393 | 		return 0; | 
 | 394 | 	} | 
 | 395 |  | 
 | 396 | 	if (lcdc.update_mode == OMAPFB_AUTO_UPDATE) { | 
 | 397 | 		disable_controller(); | 
 | 398 | 		omap_stop_lcd_dma(); | 
 | 399 | 		setup_lcd_dma(); | 
 | 400 | 		enable_controller(); | 
 | 401 | 	} | 
 | 402 |  | 
 | 403 | 	return 0; | 
 | 404 | } | 
 | 405 |  | 
 | 406 | static int omap_lcdc_enable_plane(int plane, int enable) | 
 | 407 | { | 
 | 408 | 	dev_dbg(lcdc.fbdev->dev, | 
 | 409 | 		"plane %d enable %d update_mode %d ext_mode %d\n", | 
 | 410 | 		plane, enable, lcdc.update_mode, lcdc.ext_mode); | 
 | 411 | 	if (plane != OMAPFB_PLANE_GFX) | 
 | 412 | 		return -EINVAL; | 
 | 413 |  | 
 | 414 | 	return 0; | 
 | 415 | } | 
 | 416 |  | 
 | 417 | /* | 
 | 418 |  * Configure the LCD DMA for a palette load operation and do the palette | 
 | 419 |  * downloading synchronously. We don't use the frame+palette load mode of | 
 | 420 |  * the controller, since the palette can always be downloaded seperately. | 
 | 421 |  */ | 
 | 422 | static void load_palette(void) | 
 | 423 | { | 
 | 424 | 	u16	*palette; | 
 | 425 |  | 
 | 426 | 	palette = (u16 *)lcdc.palette_virt; | 
 | 427 |  | 
 | 428 | 	*(u16 *)palette &= 0x0fff; | 
 | 429 | 	*(u16 *)palette |= lcdc.palette_code; | 
 | 430 |  | 
 | 431 | 	omap_set_lcd_dma_b1(lcdc.palette_phys, | 
 | 432 | 		lcdc.palette_size / 4 + 1, 1, OMAP_DMA_DATA_TYPE_S32); | 
 | 433 |  | 
 | 434 | 	omap_set_lcd_dma_single_transfer(1); | 
 | 435 | 	omap_setup_lcd_dma(); | 
 | 436 |  | 
 | 437 | 	init_completion(&lcdc.palette_load_complete); | 
 | 438 | 	enable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE); | 
 | 439 | 	set_load_mode(OMAP_LCDC_LOAD_PALETTE); | 
 | 440 | 	enable_controller(); | 
 | 441 | 	if (!wait_for_completion_timeout(&lcdc.palette_load_complete, | 
 | 442 | 				msecs_to_jiffies(500))) | 
 | 443 | 		dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n"); | 
 | 444 | 	/* The controller gets disabled in the irq handler */ | 
 | 445 | 	disable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE); | 
 | 446 | 	omap_stop_lcd_dma(); | 
 | 447 |  | 
 | 448 | 	omap_set_lcd_dma_single_transfer(lcdc.ext_mode); | 
 | 449 | } | 
 | 450 |  | 
 | 451 | /* Used only in internal controller mode */ | 
 | 452 | static int omap_lcdc_setcolreg(u_int regno, u16 red, u16 green, u16 blue, | 
 | 453 | 			       u16 transp, int update_hw_pal) | 
 | 454 | { | 
 | 455 | 	u16 *palette; | 
 | 456 |  | 
 | 457 | 	if (lcdc.color_mode != OMAPFB_COLOR_CLUT_8BPP || regno > 255) | 
 | 458 | 		return -EINVAL; | 
 | 459 |  | 
 | 460 | 	palette = (u16 *)lcdc.palette_virt; | 
 | 461 |  | 
 | 462 | 	palette[regno] &= ~0x0fff; | 
 | 463 | 	palette[regno] |= ((red >> 12) << 8) | ((green >> 12) << 4 ) | | 
 | 464 | 			   (blue >> 12); | 
 | 465 |  | 
 | 466 | 	if (update_hw_pal) { | 
 | 467 | 		disable_controller(); | 
 | 468 | 		omap_stop_lcd_dma(); | 
 | 469 | 		load_palette(); | 
 | 470 | 		setup_lcd_dma(); | 
 | 471 | 		set_load_mode(OMAP_LCDC_LOAD_FRAME); | 
 | 472 | 		enable_controller(); | 
 | 473 | 	} | 
 | 474 |  | 
 | 475 | 	return 0; | 
 | 476 | } | 
 | 477 |  | 
 | 478 | static void calc_ck_div(int is_tft, int pck, int *pck_div) | 
 | 479 | { | 
 | 480 | 	unsigned long lck; | 
 | 481 |  | 
 | 482 | 	pck = max(1, pck); | 
 | 483 | 	lck = clk_get_rate(lcdc.lcd_ck); | 
 | 484 | 	*pck_div = (lck + pck - 1) / pck; | 
 | 485 | 	if (is_tft) | 
 | 486 | 		*pck_div = max(2, *pck_div); | 
 | 487 | 	else | 
 | 488 | 		*pck_div = max(3, *pck_div); | 
 | 489 | 	if (*pck_div > 255) { | 
 | 490 | 		/* FIXME: try to adjust logic clock divider as well */ | 
 | 491 | 		*pck_div = 255; | 
 | 492 | 		dev_warn(lcdc.fbdev->dev, "pixclock %d kHz too low.\n", | 
 | 493 | 			 pck / 1000); | 
 | 494 | 	} | 
 | 495 | } | 
 | 496 |  | 
 | 497 | static void inline setup_regs(void) | 
 | 498 | { | 
 | 499 | 	u32 l; | 
 | 500 | 	struct lcd_panel *panel = lcdc.fbdev->panel; | 
 | 501 | 	int is_tft = panel->config & OMAP_LCDC_PANEL_TFT; | 
 | 502 | 	unsigned long lck; | 
 | 503 | 	int pcd; | 
 | 504 |  | 
 | 505 | 	l = omap_readl(OMAP_LCDC_CONTROL); | 
 | 506 | 	l &= ~OMAP_LCDC_CTRL_LCD_TFT; | 
 | 507 | 	l |= is_tft ? OMAP_LCDC_CTRL_LCD_TFT : 0; | 
 | 508 | #ifdef CONFIG_MACH_OMAP_PALMTE | 
 | 509 | /* FIXME:if (machine_is_omap_palmte()) { */ | 
 | 510 | 		/* PalmTE uses alternate TFT setting in 8BPP mode */ | 
 | 511 | 		l |= (is_tft && panel->bpp == 8) ? 0x810000 : 0; | 
 | 512 | /*	} */ | 
 | 513 | #endif | 
 | 514 | 	omap_writel(l, OMAP_LCDC_CONTROL); | 
 | 515 |  | 
 | 516 | 	l = omap_readl(OMAP_LCDC_TIMING2); | 
 | 517 | 	l &= ~(((1 << 6) - 1) << 20); | 
 | 518 | 	l |= (panel->config & OMAP_LCDC_SIGNAL_MASK) << 20; | 
 | 519 | 	omap_writel(l, OMAP_LCDC_TIMING2); | 
 | 520 |  | 
 | 521 | 	l = panel->x_res - 1; | 
 | 522 | 	l |= (panel->hsw - 1) << 10; | 
 | 523 | 	l |= (panel->hfp - 1) << 16; | 
 | 524 | 	l |= (panel->hbp - 1) << 24; | 
 | 525 | 	omap_writel(l, OMAP_LCDC_TIMING0); | 
 | 526 |  | 
 | 527 | 	l = panel->y_res - 1; | 
 | 528 | 	l |= (panel->vsw - 1) << 10; | 
 | 529 | 	l |= panel->vfp << 16; | 
 | 530 | 	l |= panel->vbp << 24; | 
 | 531 | 	omap_writel(l, OMAP_LCDC_TIMING1); | 
 | 532 |  | 
 | 533 | 	l = omap_readl(OMAP_LCDC_TIMING2); | 
 | 534 | 	l &= ~0xff; | 
 | 535 |  | 
 | 536 | 	lck = clk_get_rate(lcdc.lcd_ck); | 
 | 537 |  | 
 | 538 | 	if (!panel->pcd) | 
 | 539 | 		calc_ck_div(is_tft, panel->pixel_clock * 1000, &pcd); | 
 | 540 | 	else { | 
 | 541 | 		dev_warn(lcdc.fbdev->dev, | 
 | 542 | 		    "Pixel clock divider value is obsolete.\n" | 
 | 543 | 		    "Try to set pixel_clock to %lu and pcd to 0 " | 
 | 544 | 		    "in drivers/video/omap/lcd_%s.c and submit a patch.\n", | 
 | 545 | 			lck / panel->pcd / 1000, panel->name); | 
 | 546 |  | 
 | 547 | 		pcd = panel->pcd; | 
 | 548 | 	} | 
 | 549 | 	l |= pcd & 0xff; | 
 | 550 | 	l |= panel->acb << 8; | 
 | 551 | 	omap_writel(l, OMAP_LCDC_TIMING2); | 
 | 552 |  | 
 | 553 | 	/* update panel info with the exact clock */ | 
 | 554 | 	panel->pixel_clock = lck / pcd / 1000; | 
 | 555 | } | 
 | 556 |  | 
 | 557 | /* | 
 | 558 |  * Configure the LCD controller, download the color palette and start a looped | 
 | 559 |  * DMA transfer of the frame image data. Called only in internal | 
 | 560 |  * controller mode. | 
 | 561 |  */ | 
 | 562 | static int omap_lcdc_set_update_mode(enum omapfb_update_mode mode) | 
 | 563 | { | 
 | 564 | 	int r = 0; | 
 | 565 |  | 
 | 566 | 	if (mode != lcdc.update_mode) { | 
 | 567 | 		switch (mode) { | 
 | 568 | 		case OMAPFB_AUTO_UPDATE: | 
 | 569 | 			setup_regs(); | 
 | 570 | 			load_palette(); | 
 | 571 |  | 
 | 572 | 			/* Setup and start LCD DMA */ | 
 | 573 | 			setup_lcd_dma(); | 
 | 574 |  | 
 | 575 | 			set_load_mode(OMAP_LCDC_LOAD_FRAME); | 
 | 576 | 			enable_irqs(OMAP_LCDC_IRQ_DONE); | 
 | 577 | 			/* This will start the actual DMA transfer */ | 
 | 578 | 			enable_controller(); | 
 | 579 | 			lcdc.update_mode = mode; | 
 | 580 | 			break; | 
 | 581 | 		case OMAPFB_UPDATE_DISABLED: | 
 | 582 | 			disable_controller(); | 
 | 583 | 			omap_stop_lcd_dma(); | 
 | 584 | 			lcdc.update_mode = mode; | 
 | 585 | 			break; | 
 | 586 | 		default: | 
 | 587 | 			r = -EINVAL; | 
 | 588 | 		} | 
 | 589 | 	} | 
 | 590 |  | 
 | 591 | 	return r; | 
 | 592 | } | 
 | 593 |  | 
 | 594 | static enum omapfb_update_mode omap_lcdc_get_update_mode(void) | 
 | 595 | { | 
 | 596 | 	return lcdc.update_mode; | 
 | 597 | } | 
 | 598 |  | 
 | 599 | /* PM code called only in internal controller mode */ | 
 | 600 | static void omap_lcdc_suspend(void) | 
 | 601 | { | 
 | 602 | 	if (lcdc.update_mode == OMAPFB_AUTO_UPDATE) { | 
 | 603 | 		disable_controller(); | 
 | 604 | 		omap_stop_lcd_dma(); | 
 | 605 | 	} | 
 | 606 | } | 
 | 607 |  | 
 | 608 | static void omap_lcdc_resume(void) | 
 | 609 | { | 
 | 610 | 	if (lcdc.update_mode == OMAPFB_AUTO_UPDATE) { | 
 | 611 | 		setup_regs(); | 
 | 612 | 		load_palette(); | 
 | 613 | 		setup_lcd_dma(); | 
 | 614 | 		set_load_mode(OMAP_LCDC_LOAD_FRAME); | 
 | 615 | 		enable_irqs(OMAP_LCDC_IRQ_DONE); | 
 | 616 | 		enable_controller(); | 
 | 617 | 	} | 
 | 618 | } | 
 | 619 |  | 
 | 620 | static void omap_lcdc_get_caps(int plane, struct omapfb_caps *caps) | 
 | 621 | { | 
 | 622 | 	return; | 
 | 623 | } | 
 | 624 |  | 
 | 625 | int omap_lcdc_set_dma_callback(void (*callback)(void *data), void *data) | 
 | 626 | { | 
 | 627 | 	BUG_ON(callback == NULL); | 
 | 628 |  | 
 | 629 | 	if (lcdc.dma_callback) | 
 | 630 | 		return -EBUSY; | 
 | 631 | 	else { | 
 | 632 | 		lcdc.dma_callback = callback; | 
 | 633 | 		lcdc.dma_callback_data = data; | 
 | 634 | 	} | 
 | 635 | 	return 0; | 
 | 636 | } | 
 | 637 | EXPORT_SYMBOL(omap_lcdc_set_dma_callback); | 
 | 638 |  | 
 | 639 | void omap_lcdc_free_dma_callback(void) | 
 | 640 | { | 
 | 641 | 	lcdc.dma_callback = NULL; | 
 | 642 | } | 
 | 643 | EXPORT_SYMBOL(omap_lcdc_free_dma_callback); | 
 | 644 |  | 
 | 645 | static void lcdc_dma_handler(u16 status, void *data) | 
 | 646 | { | 
 | 647 | 	if (lcdc.dma_callback) | 
 | 648 | 		lcdc.dma_callback(lcdc.dma_callback_data); | 
 | 649 | } | 
 | 650 |  | 
 | 651 | static int mmap_kern(void) | 
 | 652 | { | 
 | 653 | 	struct vm_struct	*kvma; | 
 | 654 | 	struct vm_area_struct	vma; | 
 | 655 | 	pgprot_t		pgprot; | 
 | 656 | 	unsigned long		vaddr; | 
 | 657 |  | 
 | 658 | 	kvma = get_vm_area(lcdc.vram_size, VM_IOREMAP); | 
 | 659 | 	if (kvma == NULL) { | 
 | 660 | 		dev_err(lcdc.fbdev->dev, "can't get kernel vm area\n"); | 
 | 661 | 		return -ENOMEM; | 
 | 662 | 	} | 
 | 663 | 	vma.vm_mm = &init_mm; | 
 | 664 |  | 
 | 665 | 	vaddr = (unsigned long)kvma->addr; | 
 | 666 | 	vma.vm_start = vaddr; | 
 | 667 | 	vma.vm_end = vaddr + lcdc.vram_size; | 
 | 668 |  | 
 | 669 | 	pgprot = pgprot_writecombine(pgprot_kernel); | 
 | 670 | 	if (io_remap_pfn_range(&vma, vaddr, | 
 | 671 | 			   lcdc.vram_phys >> PAGE_SHIFT, | 
 | 672 | 			   lcdc.vram_size, pgprot) < 0) { | 
 | 673 | 		dev_err(lcdc.fbdev->dev, "kernel mmap for FB memory failed\n"); | 
 | 674 | 		return -EAGAIN; | 
 | 675 | 	} | 
 | 676 |  | 
 | 677 | 	lcdc.vram_virt = (void *)vaddr; | 
 | 678 |  | 
 | 679 | 	return 0; | 
 | 680 | } | 
 | 681 |  | 
 | 682 | static void unmap_kern(void) | 
 | 683 | { | 
 | 684 | 	vunmap(lcdc.vram_virt); | 
 | 685 | } | 
 | 686 |  | 
 | 687 | static int alloc_palette_ram(void) | 
 | 688 | { | 
 | 689 | 	lcdc.palette_virt = dma_alloc_writecombine(lcdc.fbdev->dev, | 
 | 690 | 		MAX_PALETTE_SIZE, &lcdc.palette_phys, GFP_KERNEL); | 
 | 691 | 	if (lcdc.palette_virt == NULL) { | 
 | 692 | 		dev_err(lcdc.fbdev->dev, "failed to alloc palette memory\n"); | 
 | 693 | 		return -ENOMEM; | 
 | 694 | 	} | 
 | 695 | 	memset(lcdc.palette_virt, 0, MAX_PALETTE_SIZE); | 
 | 696 |  | 
 | 697 | 	return 0; | 
 | 698 | } | 
 | 699 |  | 
 | 700 | static void free_palette_ram(void) | 
 | 701 | { | 
 | 702 | 	dma_free_writecombine(lcdc.fbdev->dev, MAX_PALETTE_SIZE, | 
 | 703 | 			lcdc.palette_virt, lcdc.palette_phys); | 
 | 704 | } | 
 | 705 |  | 
 | 706 | static int alloc_fbmem(struct omapfb_mem_region *region) | 
 | 707 | { | 
 | 708 | 	int bpp; | 
 | 709 | 	int frame_size; | 
 | 710 | 	struct lcd_panel *panel = lcdc.fbdev->panel; | 
 | 711 |  | 
 | 712 | 	bpp = panel->bpp; | 
 | 713 | 	if (bpp == 12) | 
 | 714 | 		bpp = 16; | 
 | 715 | 	frame_size = PAGE_ALIGN(panel->x_res * bpp / 8 * panel->y_res); | 
 | 716 | 	if (region->size > frame_size) | 
 | 717 | 		frame_size = region->size; | 
 | 718 | 	lcdc.vram_size = frame_size; | 
 | 719 | 	lcdc.vram_virt = dma_alloc_writecombine(lcdc.fbdev->dev, | 
 | 720 | 			lcdc.vram_size, &lcdc.vram_phys, GFP_KERNEL); | 
 | 721 | 	if (lcdc.vram_virt == NULL) { | 
 | 722 | 		dev_err(lcdc.fbdev->dev, "unable to allocate FB DMA memory\n"); | 
 | 723 | 		return -ENOMEM; | 
 | 724 | 	} | 
 | 725 | 	region->size = frame_size; | 
 | 726 | 	region->paddr = lcdc.vram_phys; | 
 | 727 | 	region->vaddr = lcdc.vram_virt; | 
 | 728 | 	region->alloc = 1; | 
 | 729 |  | 
 | 730 | 	memset(lcdc.vram_virt, 0, lcdc.vram_size); | 
 | 731 |  | 
 | 732 | 	return 0; | 
 | 733 | } | 
 | 734 |  | 
 | 735 | static void free_fbmem(void) | 
 | 736 | { | 
 | 737 | 	dma_free_writecombine(lcdc.fbdev->dev, lcdc.vram_size, | 
 | 738 | 			      lcdc.vram_virt, lcdc.vram_phys); | 
 | 739 | } | 
 | 740 |  | 
 | 741 | static int setup_fbmem(struct omapfb_mem_desc *req_md) | 
 | 742 | { | 
 | 743 | 	int r; | 
 | 744 |  | 
 | 745 | 	if (!req_md->region_cnt) { | 
 | 746 | 		dev_err(lcdc.fbdev->dev, "no memory regions defined\n"); | 
 | 747 | 		return -EINVAL; | 
 | 748 | 	} | 
 | 749 |  | 
 | 750 | 	if (req_md->region_cnt > 1) { | 
 | 751 | 		dev_err(lcdc.fbdev->dev, "only one plane is supported\n"); | 
 | 752 | 		req_md->region_cnt = 1; | 
 | 753 | 	} | 
 | 754 |  | 
 | 755 | 	if (req_md->region[0].paddr == 0) { | 
 | 756 | 		lcdc.fbmem_allocated = 1; | 
 | 757 | 		if ((r = alloc_fbmem(&req_md->region[0])) < 0) | 
 | 758 | 			return r; | 
 | 759 | 		return 0; | 
 | 760 | 	} | 
 | 761 |  | 
 | 762 | 	lcdc.vram_phys = req_md->region[0].paddr; | 
 | 763 | 	lcdc.vram_size = req_md->region[0].size; | 
 | 764 |  | 
 | 765 | 	if ((r = mmap_kern()) < 0) | 
 | 766 | 		return r; | 
 | 767 |  | 
 | 768 | 	dev_dbg(lcdc.fbdev->dev, "vram at %08x size %08lx mapped to 0x%p\n", | 
 | 769 | 		 lcdc.vram_phys, lcdc.vram_size, lcdc.vram_virt); | 
 | 770 |  | 
 | 771 | 	return 0; | 
 | 772 | } | 
 | 773 |  | 
 | 774 | static void cleanup_fbmem(void) | 
 | 775 | { | 
 | 776 | 	if (lcdc.fbmem_allocated) | 
 | 777 | 		free_fbmem(); | 
 | 778 | 	else | 
 | 779 | 		unmap_kern(); | 
 | 780 | } | 
 | 781 |  | 
 | 782 | static int omap_lcdc_init(struct omapfb_device *fbdev, int ext_mode, | 
 | 783 | 			  struct omapfb_mem_desc *req_vram) | 
 | 784 | { | 
 | 785 | 	int r; | 
 | 786 | 	u32 l; | 
 | 787 | 	int rate; | 
 | 788 | 	struct clk *tc_ck; | 
 | 789 |  | 
 | 790 | 	lcdc.irq_mask = 0; | 
 | 791 |  | 
 | 792 | 	lcdc.fbdev = fbdev; | 
 | 793 | 	lcdc.ext_mode = ext_mode; | 
 | 794 |  | 
 | 795 | 	l = 0; | 
 | 796 | 	omap_writel(l, OMAP_LCDC_CONTROL); | 
 | 797 |  | 
 | 798 | 	/* FIXME: | 
 | 799 | 	 * According to errata some platforms have a clock rate limitiation | 
 | 800 | 	 */ | 
 | 801 | 	lcdc.lcd_ck = clk_get(NULL, "lcd_ck"); | 
 | 802 | 	if (IS_ERR(lcdc.lcd_ck)) { | 
 | 803 | 		dev_err(fbdev->dev, "unable to access LCD clock\n"); | 
 | 804 | 		r = PTR_ERR(lcdc.lcd_ck); | 
 | 805 | 		goto fail0; | 
 | 806 | 	} | 
 | 807 |  | 
 | 808 | 	tc_ck = clk_get(NULL, "tc_ck"); | 
 | 809 | 	if (IS_ERR(tc_ck)) { | 
 | 810 | 		dev_err(fbdev->dev, "unable to access TC clock\n"); | 
 | 811 | 		r = PTR_ERR(tc_ck); | 
 | 812 | 		goto fail1; | 
 | 813 | 	} | 
 | 814 |  | 
 | 815 | 	rate = clk_get_rate(tc_ck); | 
 | 816 | 	clk_put(tc_ck); | 
 | 817 |  | 
 | 818 | 	if (machine_is_ams_delta()) | 
 | 819 | 		rate /= 4; | 
 | 820 | 	if (machine_is_omap_h3()) | 
 | 821 | 		rate /= 3; | 
 | 822 | 	r = clk_set_rate(lcdc.lcd_ck, rate); | 
 | 823 | 	if (r) { | 
 | 824 | 		dev_err(fbdev->dev, "failed to adjust LCD rate\n"); | 
 | 825 | 		goto fail1; | 
 | 826 | 	} | 
 | 827 | 	clk_enable(lcdc.lcd_ck); | 
 | 828 |  | 
 | 829 | 	r = request_irq(OMAP_LCDC_IRQ, lcdc_irq_handler, 0, MODULE_NAME, fbdev); | 
 | 830 | 	if (r) { | 
 | 831 | 		dev_err(fbdev->dev, "unable to get IRQ\n"); | 
 | 832 | 		goto fail2; | 
 | 833 | 	} | 
 | 834 |  | 
 | 835 | 	r = omap_request_lcd_dma(lcdc_dma_handler, NULL); | 
 | 836 | 	if (r) { | 
 | 837 | 		dev_err(fbdev->dev, "unable to get LCD DMA\n"); | 
 | 838 | 		goto fail3; | 
 | 839 | 	} | 
 | 840 |  | 
 | 841 | 	omap_set_lcd_dma_single_transfer(ext_mode); | 
 | 842 | 	omap_set_lcd_dma_ext_controller(ext_mode); | 
 | 843 |  | 
 | 844 | 	if (!ext_mode) | 
 | 845 | 		if ((r = alloc_palette_ram()) < 0) | 
 | 846 | 			goto fail4; | 
 | 847 |  | 
 | 848 | 	if ((r = setup_fbmem(req_vram)) < 0) | 
 | 849 | 		goto fail5; | 
 | 850 |  | 
 | 851 | 	pr_info("omapfb: LCDC initialized\n"); | 
 | 852 |  | 
 | 853 | 	return 0; | 
 | 854 | fail5: | 
 | 855 | 	if (!ext_mode) | 
 | 856 | 		free_palette_ram(); | 
 | 857 | fail4: | 
 | 858 | 	omap_free_lcd_dma(); | 
 | 859 | fail3: | 
 | 860 | 	free_irq(OMAP_LCDC_IRQ, lcdc.fbdev); | 
 | 861 | fail2: | 
 | 862 | 	clk_disable(lcdc.lcd_ck); | 
 | 863 | fail1: | 
 | 864 | 	clk_put(lcdc.lcd_ck); | 
 | 865 | fail0: | 
 | 866 | 	return r; | 
 | 867 | } | 
 | 868 |  | 
 | 869 | static void omap_lcdc_cleanup(void) | 
 | 870 | { | 
 | 871 | 	if (!lcdc.ext_mode) | 
 | 872 | 		free_palette_ram(); | 
 | 873 | 	cleanup_fbmem(); | 
 | 874 | 	omap_free_lcd_dma(); | 
 | 875 | 	free_irq(OMAP_LCDC_IRQ, lcdc.fbdev); | 
 | 876 | 	clk_disable(lcdc.lcd_ck); | 
 | 877 | 	clk_put(lcdc.lcd_ck); | 
 | 878 | } | 
 | 879 |  | 
 | 880 | const struct lcd_ctrl omap1_int_ctrl = { | 
 | 881 | 	.name			= "internal", | 
 | 882 | 	.init			= omap_lcdc_init, | 
 | 883 | 	.cleanup		= omap_lcdc_cleanup, | 
 | 884 | 	.get_caps		= omap_lcdc_get_caps, | 
 | 885 | 	.set_update_mode	= omap_lcdc_set_update_mode, | 
 | 886 | 	.get_update_mode	= omap_lcdc_get_update_mode, | 
 | 887 | 	.update_window		= NULL, | 
 | 888 | 	.suspend		= omap_lcdc_suspend, | 
 | 889 | 	.resume			= omap_lcdc_resume, | 
 | 890 | 	.setup_plane		= omap_lcdc_setup_plane, | 
 | 891 | 	.enable_plane		= omap_lcdc_enable_plane, | 
 | 892 | 	.setcolreg		= omap_lcdc_setcolreg, | 
 | 893 | }; |