revert between 56095 -> 55830 in arch
[AROS.git] / workbench / hidds / agp / genericbridgedeviceclass.c
blob534b0ea0637c0b406f93d6c579fa8cd1f42c0cb6
1 /*
2 Copyright © 2010-2013, The AROS Development Team. All rights reserved.
3 $Id$
4 */
6 #include <hidd/agp.h>
7 #include <hidd/pci.h>
8 #include <proto/oop.h>
9 #include <proto/exec.h>
10 #include <proto/dos.h> /* FIXME: Remove after removing Delay() */
11 #define DEBUG 0
12 #include <aros/debug.h>
14 #include "agp_private.h"
16 #undef HiddAGPBridgeDeviceAttrBase
17 #define HiddAGPBridgeDeviceAttrBase (SD(cl)->hiddAGPBridgeDeviceAB)
19 struct HiddAgpPciDevicesEnumeratorData
21 struct HIDDGenericBridgeDeviceData *gbddata;
22 OOP_AttrBase hiddPCIDeviceAB;
25 /* HELPERS */
26 UBYTE readconfigbyte(OOP_Object * pciDevice, UBYTE where)
28 struct pHidd_PCIDevice_ReadConfigByte rcbmsg = {
29 mID: OOP_GetMethodID(IID_Hidd_PCIDevice, moHidd_PCIDevice_ReadConfigByte),
30 reg: where,
31 }, *msg = &rcbmsg;
33 return (UBYTE)OOP_DoMethod(pciDevice, (OOP_Msg)msg);
36 UWORD readconfigword(OOP_Object * pciDevice, UBYTE where)
38 struct pHidd_PCIDevice_ReadConfigWord rcwmsg = {
39 mID: OOP_GetMethodID(IID_Hidd_PCIDevice, moHidd_PCIDevice_ReadConfigWord),
40 reg: where,
41 }, *msg = &rcwmsg;
43 return (UWORD)OOP_DoMethod(pciDevice, (OOP_Msg)msg);
46 ULONG readconfiglong(OOP_Object * pciDevice, UBYTE where)
48 struct pHidd_PCIDevice_ReadConfigLong rclmsg = {
49 mID: OOP_GetMethodID(IID_Hidd_PCIDevice, moHidd_PCIDevice_ReadConfigLong),
50 reg: where,
51 }, *msg = &rclmsg;
53 return (ULONG)OOP_DoMethod(pciDevice, (OOP_Msg)msg);
56 VOID writeconfiglong(OOP_Object * pciDevice, UBYTE where, ULONG val)
58 struct pHidd_PCIDevice_WriteConfigLong wclmsg = {
59 mID: OOP_GetMethodID(IID_Hidd_PCIDevice, moHidd_PCIDevice_WriteConfigLong),
60 reg: where,
61 val: val,
62 }, *msg = &wclmsg;
64 OOP_DoMethod(pciDevice, (OOP_Msg)msg);
67 VOID writeconfigbyte(OOP_Object * pciDevice, UBYTE where, UBYTE val)
69 struct pHidd_PCIDevice_WriteConfigByte wcbmsg = {
70 mID: OOP_GetMethodID(IID_Hidd_PCIDevice, moHidd_PCIDevice_WriteConfigByte),
71 reg: where,
72 val: val,
73 }, *msg = &wcbmsg;
75 OOP_DoMethod(pciDevice, (OOP_Msg)msg);
78 VOID writeconfigword(OOP_Object * pciDevice, UBYTE where, UWORD val)
80 struct pHidd_PCIDevice_WriteConfigWord wcwmsg = {
81 mID: OOP_GetMethodID(IID_Hidd_PCIDevice, moHidd_PCIDevice_WriteConfigWord),
82 reg: where,
83 val: val,
84 }, *msg = &wcwmsg;
86 OOP_DoMethod(pciDevice, (OOP_Msg)msg);
89 AROS_UFH3(void, HiddAgpPciDevicesEnumerator,
90 AROS_UFHA(struct Hook *, hook, A0),
91 AROS_UFHA(OOP_Object *, pciDevice, A2),
92 AROS_UFHA(APTR, message, A1))
94 AROS_USERFUNC_INIT
96 IPTR class, agpcapptr;
97 struct HiddAgpPciDevicesEnumeratorData * hdata =
98 (struct HiddAgpPciDevicesEnumeratorData *)hook->h_Data;
100 #undef HiddPCIDeviceAttrBase
101 #define HiddPCIDeviceAttrBase hdata->hiddPCIDeviceAB
103 OOP_GetAttr(pciDevice, aHidd_PCIDevice_Class, &class);
104 OOP_GetAttr(pciDevice, aHidd_PCIDevice_CapabilityAGP, (APTR)&agpcapptr);
106 /* Select bridges and AGP devices */
107 if ((class == 0x06) || (agpcapptr))
109 IPTR intline;
110 OOP_GetAttr(pciDevice, aHidd_PCIDevice_INTLine, &intline);
112 /* If the device is not a bridge and it has not interrupt line set, skip it */
113 if ((class != 0x06) && ((intline == 0) || (intline >= 255)))
114 return;
116 struct PciAgpDevice * pciagpdev =
117 (struct PciAgpDevice *)AllocVec(sizeof(struct PciAgpDevice), MEMF_ANY | MEMF_CLEAR);
118 IPTR temp;
119 pciagpdev->PciDevice = pciDevice;
120 pciagpdev->AgpCapability = (UBYTE)agpcapptr;
121 pciagpdev->Class = (UBYTE)class;
122 OOP_GetAttr(pciDevice, aHidd_PCIDevice_VendorID, &temp);
123 pciagpdev->VendorID = (UWORD)temp;
124 OOP_GetAttr(pciDevice, aHidd_PCIDevice_ProductID, &temp);
125 pciagpdev->ProductID = (UWORD)temp;
127 AddTail(&hdata->gbddata->devices, (struct Node *)pciagpdev);
130 #undef HiddPCIDeviceAttrBase
132 AROS_USERFUNC_EXIT
135 static ULONG HiddAgpGenericAgp3CalibrateModes(ULONG requestedmode, ULONG bridgemode, ULONG vgamode)
137 ULONG calibratedmode = bridgemode;
138 ULONG temp = 0;
140 /* Apply reserved mask to requestedmode */
141 requestedmode &= ~AGP3_RESERVED_MASK;
143 /* Select a speed for requested mode */
144 temp = requestedmode & 0x07;
145 requestedmode &= ~0x07; /* Clear any speed */
146 if (temp & AGP_STATUS_REG_AGP3_X8)
147 requestedmode |= AGP_STATUS_REG_AGP3_X8;
148 else
149 requestedmode |= AGP_STATUS_REG_AGP3_X4;
151 /* Set ARQSZ as max value. Ignore requestedmode */
152 calibratedmode = ((calibratedmode & ~AGP_STATUS_REG_ARQSZ_MASK) |
153 max(calibratedmode & AGP_STATUS_REG_ARQSZ_MASK, vgamode & AGP_STATUS_REG_ARQSZ_MASK));
155 /* Set calibration cycle. Ignore requestedmode */
156 calibratedmode = ((calibratedmode & ~AGP_STATUS_REG_CAL_MASK) |
157 min(calibratedmode & AGP_STATUS_REG_CAL_MASK, vgamode & AGP_STATUS_REG_CAL_MASK));
159 /* Set SBA for AGP3 (always) */
160 calibratedmode |= AGP_STATUS_REG_SBA;
162 /* Select speed based on request and capabilities of bridge and vgacard */
163 calibratedmode &= ~0x07; /* Clear any mode */
164 if ((requestedmode & AGP_STATUS_REG_AGP3_X8) &&
165 (bridgemode & AGP_STATUS_REG_AGP3_X8) &&
166 (vgamode & AGP_STATUS_REG_AGP3_X8))
167 calibratedmode |= AGP_STATUS_REG_AGP3_X8;
168 else
169 calibratedmode |= AGP_STATUS_REG_AGP3_X4;
171 return calibratedmode;
174 static ULONG HiddAgpGenericAgp2CalibrateModes(ULONG requestedmode, ULONG bridgemode, ULONG vgamode)
176 ULONG calibratedmode = bridgemode;
177 ULONG temp = 0;
179 /* Apply reserved mask to requestedmode */
180 requestedmode &= ~AGP2_RESERVED_MASK;
182 /* Fix for some bridges reporting only one speed instead of all */
183 if (bridgemode & AGP_STATUS_REG_AGP2_X4)
184 bridgemode |= (AGP_STATUS_REG_AGP2_X2 | AGP_STATUS_REG_AGP2_X1);
185 if (bridgemode & AGP_STATUS_REG_AGP2_X2)
186 bridgemode |= AGP_STATUS_REG_AGP2_X1;
188 /* Select speed for requested mode */
189 temp = requestedmode & 0x07;
190 requestedmode &= ~0x07; /* Clear any speed */
191 if (temp & AGP_STATUS_REG_AGP2_X4)
192 requestedmode |= AGP_STATUS_REG_AGP2_X4;
193 else
195 if (temp & AGP_STATUS_REG_AGP2_X2)
196 requestedmode |= AGP_STATUS_REG_AGP2_X2;
197 else
198 requestedmode |= AGP_STATUS_REG_AGP2_X1;
201 /* Disable SBA if not supported/requested */
202 if (!((bridgemode & AGP_STATUS_REG_SBA) && (requestedmode & AGP_STATUS_REG_SBA)
203 && (vgamode & AGP_STATUS_REG_SBA)))
204 calibratedmode &= ~AGP_STATUS_REG_SBA;
206 /* Select speed based on request and capabilities of bridge and vgacard */
207 calibratedmode &= ~0x07; /* Clear any mode */
208 if ((requestedmode & AGP_STATUS_REG_AGP2_X4) &&
209 (bridgemode & AGP_STATUS_REG_AGP2_X4) &&
210 (vgamode & AGP_STATUS_REG_AGP2_X4))
211 calibratedmode |= AGP_STATUS_REG_AGP2_X4;
212 else
214 if ((requestedmode & AGP_STATUS_REG_AGP2_X2) &&
215 (bridgemode & AGP_STATUS_REG_AGP2_X2) &&
216 (vgamode & AGP_STATUS_REG_AGP2_X2))
217 calibratedmode |= AGP_STATUS_REG_AGP2_X2;
218 else
219 calibratedmode |= AGP_STATUS_REG_AGP2_X1;
222 /* Disable fast writed if in X1 mode */
223 if (calibratedmode & AGP_STATUS_REG_AGP2_X1)
224 calibratedmode &= ~AGP_STATUS_REG_FAST_WRITES;
226 return calibratedmode;
229 static ULONG HiddAgpGenericSelectBestMode(struct HIDDGenericBridgeDeviceData * gbddata, ULONG requestedmode, ULONG bridgemode)
231 OOP_Object * videodev = gbddata->videocard->PciDevice;
232 UBYTE videoagpcap = gbddata->videocard->AgpCapability;
233 ULONG vgamode = 0;
235 /* Get VGA card capability */
236 vgamode = readconfiglong(videodev, videoagpcap + AGP_STATUS_REG);
238 D(bug("[AGP] VGA mode 0x%x\n", vgamode));
240 /* Set Request Queue */
241 bridgemode = ((bridgemode & ~AGP_STATUS_REG_RQ_DEPTH_MASK) |
242 min(requestedmode & AGP_STATUS_REG_RQ_DEPTH_MASK,
243 min(bridgemode & AGP_STATUS_REG_RQ_DEPTH_MASK, vgamode & AGP_STATUS_REG_RQ_DEPTH_MASK)));
245 /* Fast Writes */
246 if (!(
247 (bridgemode & AGP_STATUS_REG_FAST_WRITES) &&
248 (requestedmode & AGP_STATUS_REG_FAST_WRITES) &&
249 (vgamode & AGP_STATUS_REG_FAST_WRITES)))
251 bridgemode &= ~AGP_STATUS_REG_FAST_WRITES;
254 if (gbddata->bridgemode & AGP_STATUS_REG_AGP_3_0)
256 bridgemode = HiddAgpGenericAgp3CalibrateModes(requestedmode, bridgemode, vgamode);
258 else
260 bridgemode = HiddAgpGenericAgp2CalibrateModes(requestedmode, bridgemode, vgamode);
263 return bridgemode;
266 static VOID HiddAgpGenericSendCommand(struct HIDDGenericBridgeDeviceData * gbddata, ULONG status)
268 struct PciAgpDevice * pciagpdev = NULL;
270 /* Send command to all AGP capable devices */
271 ForeachNode(&gbddata->devices, pciagpdev)
273 if(pciagpdev->AgpCapability)
275 ULONG mode = status;
277 mode &= 0x7;
278 if (status & AGP_STATUS_REG_AGP_3_0)
279 mode *= 4;
281 D(bug("[AGP] Set AGP%d device 0x%x/0x%x to speed %dx\n",
282 (status & AGP_STATUS_REG_AGP_3_0) ? 3 : 2,
283 pciagpdev->VendorID, pciagpdev->ProductID, mode));
285 writeconfiglong(pciagpdev->PciDevice, pciagpdev->AgpCapability + AGP_COMMAND_REG, status);
287 /* FIXME: Change this to timer.device interaction. DOS may not be up when agp.hidd is used */
288 /* Keep this delay here. Some bridges need it. */
289 Delay(10);
294 /* NON-PUBLIC METHODS */
295 BOOL METHOD(GenericBridgeDevice, Hidd_AGPBridgeDevice, ScanAndDetectDevices)
297 struct HIDDGenericBridgeDeviceData * gbddata = OOP_INST_DATA(cl, o);
298 struct PciAgpDevice * pciagpdev = NULL;
300 if (!SD(cl)->pcibus)
301 return FALSE;
303 /* Scan all PCI devices */
304 struct HiddAgpPciDevicesEnumeratorData hdata = {
305 gbddata : gbddata,
306 hiddPCIDeviceAB : SD(cl)->hiddPCIDeviceAB
309 struct Hook FindHook = {
310 h_Entry: (IPTR (*)())HiddAgpPciDevicesEnumerator,
311 h_Data: &hdata,
314 struct TagItem Requirements[] = {
315 { TAG_DONE, 0UL }
318 struct pHidd_PCI_EnumDevices enummsg = {
319 mID: OOP_GetMethodID(IID_Hidd_PCI, moHidd_PCI_EnumDevices),
320 callback: &FindHook,
321 requirements: (struct TagItem*)&Requirements,
324 OOP_DoMethod(SD(cl)->pcibus, (OOP_Msg)&enummsg);
326 /* Select matching devices */
327 ForeachNode(&gbddata->devices, pciagpdev)
329 /* Select bridge */
330 if ((!gbddata->bridge) && (pciagpdev->Class == 0x06) &&
331 (pciagpdev->AgpCapability))
333 gbddata->bridge = pciagpdev;
336 /* Select video card */
337 if ((!gbddata->videocard) && (pciagpdev->Class == 0x03) &&
338 (pciagpdev->AgpCapability))
340 gbddata->videocard = pciagpdev;
344 return (gbddata->videocard && gbddata->bridge);
347 BOOL METHOD(GenericBridgeDevice, Hidd_AGPBridgeDevice, CreateGattTable)
349 struct HIDDGenericBridgeDeviceData * gbddata = OOP_INST_DATA(cl, o);
351 if (gbddata->bridgeapersize == 0)
352 return FALSE;
354 /* Create a table that will hold a certain number of 32bit pointers */
355 ULONG entries = gbddata->bridgeapersize * 1024 * 1024 / 4096;
356 ULONG tablesize = entries * 4;
357 ULONG i = 0;
359 gbddata->scratchmembuffer = AllocVec(4096 * 2, MEMF_PUBLIC | MEMF_CLEAR);
360 gbddata->scratchmem = (ULONG*)(ALIGN((IPTR)gbddata->scratchmembuffer, 4096));
361 D(bug("[AGP] Created scratch memory at 0x%x\n", (ULONG)gbddata->scratchmem));
364 gbddata->gatttablebuffer = AllocVec(tablesize + 4096, MEMF_PUBLIC | MEMF_CLEAR);
365 gbddata->gatttable = (ULONG *)(ALIGN((IPTR)gbddata->gatttablebuffer, 4096));
367 D(bug("[AGP] Created GATT table size %d at 0x%x\n", tablesize,
368 (ULONG)gbddata->gatttable));
370 for (i = 0; i < entries; i++)
372 writel((ULONG)(IPTR)gbddata->scratchmem, gbddata->gatttable + i);
373 readl(gbddata->gatttable + i); /* PCI Posting. */
376 flushcpucache();
378 return TRUE;
381 VOID METHOD(GenericBridgeDevice, Hidd_AGPBridgeDevice, FlushGattTable)
383 bug("[GenericBridgeDevice] abstract FlushGattTable method called\n");
386 /* PUBLIC METHODS */
387 OOP_Object * METHOD(GenericBridgeDevice, Root, New)
389 o = (OOP_Object *)OOP_DoSuperMethod(cl, o, (OOP_Msg) msg);
391 if (o)
393 struct HIDDGenericBridgeDeviceData * gbddata = OOP_INST_DATA(cl, o);
394 gbddata->bridge = NULL;
395 gbddata->bridgemode = 0;
396 gbddata->bridgeapersize = 0;
397 gbddata->bridgeaperbase = 0;
398 gbddata->gatttable = NULL;
399 gbddata->gatttablebuffer = NULL;
400 gbddata->scratchmem = NULL;
401 gbddata->scratchmembuffer = NULL;
402 gbddata->videocard = NULL;
403 NEWLIST(&gbddata->devices);
404 gbddata->state = STATE_UNKNOWN;
405 gbddata->memmask = 0x00000000;
406 InitSemaphore(&gbddata->lock);
409 return o;
412 VOID GenericBridgeDevice__Root__Dispose(OOP_Class * cl, OOP_Object * o, OOP_Msg msg)
414 struct HIDDGenericBridgeDeviceData * gbddata = OOP_INST_DATA(cl, o);
415 struct PciAgpDevice * pciagpdev = NULL;
417 /* Free scanned device information */
418 while((pciagpdev = (struct PciAgpDevice *)RemHead(&gbddata->devices)) != NULL)
419 FreeVec(pciagpdev);
421 /* Free GATT table */
422 D(bug("[AGP] Freeing GATT table 0x%x, scratch memory 0x%x\n",
423 (ULONG)gbddata->gatttable, (ULONG)gbddata->scratchmem));
425 FreeVec(gbddata->scratchmembuffer);
426 gbddata->scratchmembuffer = NULL;
427 gbddata->scratchmem = NULL;
429 FreeVec(gbddata->gatttablebuffer);
430 gbddata->gatttablebuffer = NULL;
431 gbddata->gatttable = NULL;
433 OOP_DoSuperMethod(cl, o, (OOP_Msg)msg);
436 VOID METHOD(GenericBridgeDevice, Root, Get)
438 ULONG idx;
439 struct HIDDGenericBridgeDeviceData * gbddata = OOP_INST_DATA(cl, o);
441 if (IS_AGPBRIDGEDEV_ATTR(msg->attrID, idx))
443 switch (idx)
445 case aoHidd_AGPBridgeDevice_Mode:
446 *msg->storage = gbddata->bridgemode;
447 return;
449 case aoHidd_AGPBridgeDevice_ApertureBase:
450 *msg->storage = gbddata->bridgeaperbase;
451 return;
453 case aoHidd_AGPBridgeDevice_ApertureSize:
454 *msg->storage = gbddata->bridgeapersize;
455 return;
459 /* Use parent class for all other properties */
460 OOP_DoSuperMethod(cl, o, (OOP_Msg)msg);
463 BOOL METHOD(GenericBridgeDevice, Hidd_AGPBridgeDevice, Enable)
465 struct HIDDGenericBridgeDeviceData * gbddata = OOP_INST_DATA(cl, o);
467 ObtainSemaphore(&gbddata->lock);
469 if (gbddata->state != STATE_INITIALIZED)
471 ReleaseSemaphore(&gbddata->lock);
472 return FALSE;
475 OOP_Object * bridgedev = gbddata->bridge->PciDevice;
476 UBYTE bridgeagpcap = gbddata->bridge->AgpCapability;
477 ULONG requestedmode = msg->requestedmode;
478 ULONG bridgemode = 0;
479 ULONG major = 0;
482 D(bug("[AGP] Enable AGP:\n"));
483 D(bug("[AGP] Requested mode 0x%x\n", requestedmode));
485 bridgemode = readconfiglong(bridgedev, bridgeagpcap + AGP_STATUS_REG);
486 D(bug("[AGP] Bridge mode 0x%x\n", requestedmode));
488 bridgemode = HiddAgpGenericSelectBestMode(gbddata, requestedmode, bridgemode);
490 bridgemode |= AGP_STATUS_REG_AGP_ENABLED;
492 major = (readconfigbyte(bridgedev, bridgeagpcap + AGP_VERSION_REG) >> 4) & 0xf;
494 if (major >= 3)
496 /* Bridge supports version 3 or greater) */
497 if (gbddata->bridgemode & AGP_STATUS_REG_AGP_3_0)
499 /* Bridge is operating in mode 3.0 */
501 else
503 /* Bridge is operating in legacy mode */
504 /* Disable calibration cycle */
505 ULONG temp = 0;
506 bridgemode &= ~(7 << 10);
507 temp = readconfiglong(bridgedev, bridgeagpcap + AGP_CTRL_REG);
508 temp |= (1 << 9);
509 writeconfiglong(bridgedev, bridgeagpcap + AGP_CTRL_REG, temp);
513 D(bug("[AGP] Mode to write: 0x%x\n", bridgemode));
515 HiddAgpGenericSendCommand(gbddata, bridgemode);
516 gbddata->state = STATE_ENABLED;
518 ReleaseSemaphore(&gbddata->lock);
520 return TRUE;
523 VOID METHOD(GenericBridgeDevice, Hidd_AGPBridgeDevice, BindMemory)
525 ULONG i;
526 struct HIDDGenericBridgeDeviceData * gbddata = OOP_INST_DATA(cl, o);
528 D(bug("Bind address 0x%x into offset %d, size %d\n", (ULONG)msg->address, msg->offset, msg->size));
530 if (gbddata->state != STATE_ENABLED)
531 return;
533 ObtainSemaphore(&gbddata->lock);
535 /* TODO: check if offset + size / 4096 ends before gatt_table end */
537 /* TODO: get mask type */
539 /* TODO: Check if each entry in GATT to be written is unbound */
541 /* Flush incomming memory - will be done in flush_cpu_cache below */
543 /* Insert entries into GATT table */
544 for(i = 0; i < msg->size / 4096; i++)
546 /* Write masked memory address into GATT */
547 writel((msg->address + (4096 * i)) | gbddata->memmask,
548 gbddata->gatttable + msg->offset + i);
551 readl(gbddata->gatttable + msg->offset + i - 1); /* PCI posting */
553 /* Flush CPU cache - make sure data in GATT is up to date */
554 flushcpucache();
556 /* Flush GATT table at card */
557 struct pHidd_AGPBridgeDevice_FlushGattTable fgtmsg = {
558 mID: OOP_GetMethodID(IID_Hidd_AGPBridgeDevice, moHidd_AGPBridgeDevice_FlushGattTable)
561 OOP_DoMethod(o, (OOP_Msg)&fgtmsg);
563 ReleaseSemaphore(&gbddata->lock);
566 VOID METHOD(GenericBridgeDevice, Hidd_AGPBridgeDevice, UnBindMemory)
568 ULONG i;
569 struct HIDDGenericBridgeDeviceData * gbddata = OOP_INST_DATA(cl, o);
571 D(bug("Unbind offset %d, size %d\n", msg->offset, msg->size));
573 if (gbddata->state != STATE_ENABLED)
574 return;
576 if (msg->size == 0)
577 return;
579 ObtainSemaphore(&gbddata->lock);
581 /* TODO: get mask type */
583 /* Remove entries from GATT table */
584 for(i = 0; i < msg->size / 4096; i++)
586 writel((ULONG)(IPTR)gbddata->scratchmem, gbddata->gatttable + msg->offset + i);
589 readl(gbddata->gatttable + msg->offset + i - 1); /* PCI posting */
591 /* Flush CPU cache - make sure data in GATT is up to date */
592 flushcpucache();
594 /* Flush GATT table */
595 struct pHidd_AGPBridgeDevice_FlushGattTable fgtmsg = {
596 mID: OOP_GetMethodID(IID_Hidd_AGPBridgeDevice, moHidd_AGPBridgeDevice_FlushGattTable)
599 OOP_DoMethod(o, (OOP_Msg)&fgtmsg);
601 ReleaseSemaphore(&gbddata->lock);
604 VOID METHOD(GenericBridgeDevice, Hidd_AGPBridgeDevice, FlushChipset)
606 /* This function is a NOOP */
609 BOOL METHOD(GenericBridgeDevice, Hidd_AGPBridgeDevice, Initialize)
611 bug("[GenericBridgeDevice] abstract Initialize method called\n");
612 return FALSE;