]> Pileus Git - ~andy/linux/blob - drivers/media/video/saa7164/saa7164-dvb.c
V4L/DVB (12923): SAA7164: Add support for the NXP SAA7164 silicon
[~andy/linux] / drivers / media / video / saa7164 / saa7164-dvb.c
1 /*
2  *  Driver for the NXP SAA7164 PCIe bridge
3  *
4  *  Copyright (c) 2009 Steven Toth <stoth@kernellabs.com>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include "saa7164.h"
23
24 #include "tda10048.h"
25 #include "tda18271.h"
26 #include "s5h1411.h"
27
28 #define DRIVER_NAME "saa7164"
29
30 DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
31
32 /* addr is in the card struct, get it from there */
33 static struct tda10048_config hauppauge_hvr2200_1_config = {
34         .demod_address    = 0x10 >> 1,
35         .output_mode      = TDA10048_SERIAL_OUTPUT,
36         .fwbulkwritelen   = TDA10048_BULKWRITE_200,
37         .inversion        = TDA10048_INVERSION_ON
38 };
39 static struct tda10048_config hauppauge_hvr2200_2_config = {
40         .demod_address    = 0x12 >> 1,
41         .output_mode      = TDA10048_SERIAL_OUTPUT,
42         .fwbulkwritelen   = TDA10048_BULKWRITE_200,
43         .inversion        = TDA10048_INVERSION_ON
44 };
45
46 static struct tda18271_std_map hauppauge_tda18271_std_map = {
47         .atsc_6   = { .if_freq = 3250, .agc_mode = 3, .std = 3,
48                       .if_lvl = 6, .rfagc_top = 0x37 },
49         .qam_6    = { .if_freq = 4000, .agc_mode = 3, .std = 0,
50                       .if_lvl = 6, .rfagc_top = 0x37 },
51 };
52
53 static struct tda18271_config hauppauge_hvr22x0_tuner_config = {
54         .std_map        = &hauppauge_tda18271_std_map,
55         .gate           = TDA18271_GATE_ANALOG,
56 };
57
58 static struct s5h1411_config hauppauge_s5h1411_config = {
59         .output_mode   = S5H1411_SERIAL_OUTPUT,
60         .gpio          = S5H1411_GPIO_ON,
61         .qam_if        = S5H1411_IF_4000,
62         .vsb_if        = S5H1411_IF_3250,
63         .inversion     = S5H1411_INVERSION_ON,
64         .status_mode   = S5H1411_DEMODLOCKING,
65         .mpeg_timing   = S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK,
66 };
67
68 static int saa7164_dvb_stop_tsport(struct saa7164_tsport *port)
69 {
70         struct saa7164_dev *dev = port->dev;
71         int ret;
72
73         ret = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
74         if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
75                 printk(KERN_ERR "%s() stop transition failed, ret = 0x%x\n",
76                         __func__, ret);
77                 ret = -EIO;
78         } else {
79                 dprintk(DBGLVL_DVB, "%s()    Stopped\n", __func__);
80                 ret = 0;
81         }
82
83         return ret;
84 }
85
86 static int saa7164_dvb_acquire_tsport(struct saa7164_tsport *port)
87 {
88         struct saa7164_dev *dev = port->dev;
89         int ret;
90
91         ret = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE);
92         if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
93                 printk(KERN_ERR "%s() acquire transition failed, ret = 0x%x\n",
94                         __func__, ret);
95                 ret = -EIO;
96         } else {
97                 dprintk(DBGLVL_DVB, "%s() Acquired\n", __func__);
98                 ret = 0;
99         }
100
101         return ret;
102 }
103
104 static int saa7164_dvb_pause_tsport(struct saa7164_tsport *port)
105 {
106         struct saa7164_dev *dev = port->dev;
107         int ret;
108
109         ret = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE);
110         if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) {
111                 printk(KERN_ERR "%s() pause transition failed, ret = 0x%x\n",
112                         __func__, ret);
113                 ret = -EIO;
114         } else {
115                 dprintk(DBGLVL_DVB, "%s()   Paused\n", __func__);
116                 ret = 0;
117         }
118
119         return ret;
120 }
121
122 /* Firmware is very windows centric, meaning you have to transition
123  * the part through AVStream / KS Windows stages, forwards or backwards.
124  * States are: stopped, acquired (h/w), paused, started.
125  */
126 static int saa7164_dvb_stop_streaming(struct saa7164_tsport *port)
127 {
128         struct saa7164_dev *dev = port->dev;
129         int ret;
130
131         dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
132
133         ret = saa7164_dvb_pause_tsport(port);
134         ret = saa7164_dvb_acquire_tsport(port);
135         ret = saa7164_dvb_stop_tsport(port);
136
137         return ret;
138 }
139
140 static int saa7164_dvb_cfg_tsport(struct saa7164_tsport *port)
141 {
142         tmHWStreamParameters_t *params = &port->hw_streamingparams;
143         struct saa7164_dev *dev = port->dev;
144         struct saa7164_buffer *buf;
145         struct list_head *c, *n;
146         int i = 0;
147
148         dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
149
150         saa7164_writel(port->pitch, params->pitch);
151         saa7164_writel(port->bufsize, params->pitch * params->numberoflines);
152
153         dprintk(DBGLVL_DVB, " configured:\n");
154         dprintk(DBGLVL_DVB, "   lmmio       0x%llx\n", (u64)dev->lmmio);
155         dprintk(DBGLVL_DVB, "   bufcounter  0x%x = 0x%x\n", port->bufcounter,
156                 saa7164_readl(port->bufcounter));
157
158         dprintk(DBGLVL_DVB, "   pitch       0x%x = %d\n", port->pitch,
159                 saa7164_readl(port->pitch));
160
161         dprintk(DBGLVL_DVB, "   bufsize     0x%x = %d\n", port->bufsize,
162                 saa7164_readl(port->bufsize));
163
164         dprintk(DBGLVL_DVB, "   buffercount = %d\n", port->hwcfg.buffercount);
165         dprintk(DBGLVL_DVB, "   bufoffset = 0x%x\n", port->bufoffset);
166         dprintk(DBGLVL_DVB, "   bufptr32h = 0x%x\n", port->bufptr32h);
167         dprintk(DBGLVL_DVB, "   bufptr32l = 0x%x\n", port->bufptr32l);
168
169         /* Poke the buffers and offsets into PCI space */
170         mutex_lock(&port->dmaqueue_lock);
171         list_for_each_safe(c, n, &port->dmaqueue.list) {
172                 buf = list_entry(c, struct saa7164_buffer, list);
173
174                 /* TODO: Review this in light of 32v64 assignments */
175                 saa7164_writel(port->bufoffset + (sizeof(u32) * i), 0);
176                 saa7164_writel(port->bufptr32h + ((sizeof(u32) * 2) * i),
177                         buf->pt_dma);
178                 saa7164_writel(port->bufptr32l + ((sizeof(u32) * 2) * i), 0);
179
180                 dprintk(DBGLVL_DVB,
181                         "   buf[%d] offset 0x%lx (0x%x) "
182                         "buf 0x%lx/%lx (0x%x/%x)\n",
183                         i,
184                         port->bufoffset + (i * sizeof(u32)),
185                         saa7164_readl(port->bufoffset + (sizeof(u32) * i)),
186                         port->bufptr32h + ((sizeof(u32) * 2) * i),
187                         port->bufptr32l + ((sizeof(u32) * 2) * i),
188                         saa7164_readl(port->bufptr32h + ((sizeof(u32) * i)
189                                 * 2)),
190                         saa7164_readl(port->bufptr32l + ((sizeof(u32) * i)
191                                 * 2)));
192
193                 if (i++ > port->hwcfg.buffercount)
194                         BUG();
195
196         }
197         mutex_unlock(&port->dmaqueue_lock);
198
199         return 0;
200 }
201
202 static int saa7164_dvb_start_tsport(struct saa7164_tsport *port)
203 {
204         struct saa7164_dev *dev = port->dev;
205         int ret = 0, result;
206
207         dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
208
209         saa7164_dvb_cfg_tsport(port);
210
211         /* Acquire the hardware */
212         result = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE);
213         if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
214                 printk(KERN_ERR "%s() acquire transition failed, res = 0x%x\n",
215                         __func__, result);
216
217                 /* Stop the hardware, regardless */
218                 result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
219                 if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
220                         printk(KERN_ERR "%s() acquire/forced stop transition "
221                                 "failed, res = 0x%x\n", __func__, result);
222                 }
223                 ret = -EIO;
224                 goto out;
225         } else
226                 dprintk(DBGLVL_DVB, "%s()   Acquired\n", __func__);
227
228         /* Pause the hardware */
229         result = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE);
230         if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
231                 printk(KERN_ERR "%s() pause transition failed, res = 0x%x\n",
232                                 __func__, result);
233
234                 /* Stop the hardware, regardless */
235                 result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
236                 if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
237                         printk(KERN_ERR "%s() pause/forced stop transition "
238                                 "failed, res = 0x%x\n", __func__, result);
239                 }
240
241                 ret = -EIO;
242                 goto out;
243         } else
244                 dprintk(DBGLVL_DVB, "%s()   Paused\n", __func__);
245
246         /* Start the hardware */
247         result = saa7164_api_transition_port(port, SAA_DMASTATE_RUN);
248         if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
249                 printk(KERN_ERR "%s() run transition failed, result = 0x%x\n",
250                                 __func__, result);
251
252                 /* Stop the hardware, regardless */
253                 result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP);
254                 if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) {
255                         printk(KERN_ERR "%s() run/forced stop transition "
256                                 "failed, res = 0x%x\n", __func__, result);
257                 }
258
259                 ret = -EIO;
260         } else
261                 dprintk(DBGLVL_DVB, "%s()   Running\n", __func__);
262
263 out:
264         return ret;
265 }
266
267 static int saa7164_dvb_start_feed(struct dvb_demux_feed *feed)
268 {
269         struct dvb_demux *demux = feed->demux;
270         struct saa7164_tsport *port = (struct saa7164_tsport *) demux->priv;
271         struct saa7164_dvb *dvb = &port->dvb;
272         struct saa7164_dev *dev = port->dev;
273         int ret = 0;
274
275         dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
276
277         if (!demux->dmx.frontend)
278                 return -EINVAL;
279
280         if (dvb) {
281                 mutex_lock(&dvb->lock);
282                 if (dvb->feeding++ == 0) {
283                         /* Start transport */
284                         ret = saa7164_dvb_start_tsport(port);
285                 }
286                 mutex_unlock(&dvb->lock);
287                 dprintk(DBGLVL_DVB, "%s(port=%d) now feeding = %d\n",
288                         __func__, port->nr, dvb->feeding);
289         }
290
291         return ret;
292 }
293
294 static int saa7164_dvb_stop_feed(struct dvb_demux_feed *feed)
295 {
296         struct dvb_demux *demux = feed->demux;
297         struct saa7164_tsport *port = (struct saa7164_tsport *) demux->priv;
298         struct saa7164_dvb *dvb = &port->dvb;
299         struct saa7164_dev *dev = port->dev;
300         int ret = 0;
301
302         dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
303
304         if (dvb) {
305                 mutex_lock(&dvb->lock);
306                 if (--dvb->feeding == 0) {
307                         /* Stop transport */
308                         ret = saa7164_dvb_stop_streaming(port);
309                 }
310                 mutex_unlock(&dvb->lock);
311                 dprintk(DBGLVL_DVB, "%s(port=%d) now feeding = %d\n",
312                         __func__, port->nr, dvb->feeding);
313         }
314
315         return ret;
316 }
317
318 static int dvb_register(struct saa7164_tsport *port)
319 {
320         struct saa7164_dvb *dvb = &port->dvb;
321         struct saa7164_dev *dev = port->dev;
322         struct saa7164_buffer *buf;
323         int result, i;
324
325         dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr);
326
327         /* Sanity check that the PCI configuration space is active */
328         if (port->hwcfg.BARLocation == 0) {
329                 result = -ENOMEM;
330                 printk(KERN_ERR "%s: dvb_register_adapter failed "
331                        "(errno = %d), NO PCI configuration\n",
332                         DRIVER_NAME, result);
333                 goto fail_adapter;
334         }
335
336         /* Init and establish defaults */
337         port->hw_streamingparams.bitspersample = 8;
338         port->hw_streamingparams.samplesperline = 188;
339         port->hw_streamingparams.numberoflines =
340                 (SAA7164_TS_NUMBER_OF_LINES * 188) / 188;
341
342         port->hw_streamingparams.pitch = 188;
343         port->hw_streamingparams.linethreshold = 0;
344         port->hw_streamingparams.pagetablelistvirt = 0;
345         port->hw_streamingparams.pagetablelistphys = 0;
346         port->hw_streamingparams.numpagetables = 2 +
347                 ((SAA7164_TS_NUMBER_OF_LINES * 188) / PAGE_SIZE);
348
349         port->hw_streamingparams.numpagetableentries = port->hwcfg.buffercount;
350
351         /* Allocate the PCI resources */
352         for (i = 0; i < port->hwcfg.buffercount; i++) {
353                 buf = saa7164_buffer_alloc(port,
354                         port->hw_streamingparams.numberoflines *
355                         port->hw_streamingparams.pitch);
356
357                 if (!buf) {
358                         result = -ENOMEM;
359                         printk(KERN_ERR "%s: dvb_register_adapter failed "
360                                "(errno = %d), unable to allocate buffers\n",
361                                 DRIVER_NAME, result);
362                         goto fail_adapter;
363                 }
364                 buf->nr = i;
365
366                 mutex_lock(&port->dmaqueue_lock);
367                 list_add_tail(&buf->list, &port->dmaqueue.list);
368                 mutex_unlock(&port->dmaqueue_lock);
369         }
370
371         /* register adapter */
372         result = dvb_register_adapter(&dvb->adapter, DRIVER_NAME, THIS_MODULE,
373                         &dev->pci->dev, adapter_nr);
374         if (result < 0) {
375                 printk(KERN_ERR "%s: dvb_register_adapter failed "
376                        "(errno = %d)\n", DRIVER_NAME, result);
377                 goto fail_adapter;
378         }
379         dvb->adapter.priv = port;
380
381         /* register frontend */
382         result = dvb_register_frontend(&dvb->adapter, dvb->frontend);
383         if (result < 0) {
384                 printk(KERN_ERR "%s: dvb_register_frontend failed "
385                        "(errno = %d)\n", DRIVER_NAME, result);
386                 goto fail_frontend;
387         }
388
389         /* register demux stuff */
390         dvb->demux.dmx.capabilities =
391                 DMX_TS_FILTERING | DMX_SECTION_FILTERING |
392                 DMX_MEMORY_BASED_FILTERING;
393         dvb->demux.priv       = port;
394         dvb->demux.filternum  = 256;
395         dvb->demux.feednum    = 256;
396         dvb->demux.start_feed = saa7164_dvb_start_feed;
397         dvb->demux.stop_feed  = saa7164_dvb_stop_feed;
398         result = dvb_dmx_init(&dvb->demux);
399         if (result < 0) {
400                 printk(KERN_ERR "%s: dvb_dmx_init failed (errno = %d)\n",
401                        DRIVER_NAME, result);
402                 goto fail_dmx;
403         }
404
405         dvb->dmxdev.filternum    = 256;
406         dvb->dmxdev.demux        = &dvb->demux.dmx;
407         dvb->dmxdev.capabilities = 0;
408         result = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter);
409         if (result < 0) {
410                 printk(KERN_ERR "%s: dvb_dmxdev_init failed (errno = %d)\n",
411                        DRIVER_NAME, result);
412                 goto fail_dmxdev;
413         }
414
415         dvb->fe_hw.source = DMX_FRONTEND_0;
416         result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw);
417         if (result < 0) {
418                 printk(KERN_ERR "%s: add_frontend failed "
419                        "(DMX_FRONTEND_0, errno = %d)\n", DRIVER_NAME, result);
420                 goto fail_fe_hw;
421         }
422
423         dvb->fe_mem.source = DMX_MEMORY_FE;
424         result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem);
425         if (result < 0) {
426                 printk(KERN_ERR "%s: add_frontend failed "
427                        "(DMX_MEMORY_FE, errno = %d)\n", DRIVER_NAME, result);
428                 goto fail_fe_mem;
429         }
430
431         result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw);
432         if (result < 0) {
433                 printk(KERN_ERR "%s: connect_frontend failed (errno = %d)\n",
434                        DRIVER_NAME, result);
435                 goto fail_fe_conn;
436         }
437
438         /* register network adapter */
439         dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx);
440         return 0;
441
442 fail_fe_conn:
443         dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
444 fail_fe_mem:
445         dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
446 fail_fe_hw:
447         dvb_dmxdev_release(&dvb->dmxdev);
448 fail_dmxdev:
449         dvb_dmx_release(&dvb->demux);
450 fail_dmx:
451         dvb_unregister_frontend(dvb->frontend);
452 fail_frontend:
453         dvb_frontend_detach(dvb->frontend);
454         dvb_unregister_adapter(&dvb->adapter);
455 fail_adapter:
456         return result;
457 }
458
459 int saa7164_dvb_unregister(struct saa7164_tsport *port)
460 {
461         struct saa7164_dvb *dvb = &port->dvb;
462         struct saa7164_dev *dev = port->dev;
463         struct saa7164_buffer *b;
464         struct list_head *c, *n;
465
466         dprintk(DBGLVL_DVB, "%s()\n", __func__);
467
468         /* Remove any allocated buffers */
469         mutex_lock(&port->dmaqueue_lock);
470         list_for_each_safe(c, n, &port->dmaqueue.list) {
471                 b = list_entry(c, struct saa7164_buffer, list);
472                 list_del(c);
473                 saa7164_buffer_dealloc(port, b);
474         }
475         mutex_unlock(&port->dmaqueue_lock);
476
477         if (dvb->frontend == NULL)
478                 return 0;
479
480         dvb_net_release(&dvb->net);
481         dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
482         dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
483         dvb_dmxdev_release(&dvb->dmxdev);
484         dvb_dmx_release(&dvb->demux);
485         dvb_unregister_frontend(dvb->frontend);
486         dvb_frontend_detach(dvb->frontend);
487         dvb_unregister_adapter(&dvb->adapter);
488         return 0;
489 }
490
491 /* All the DVB attach calls go here, this function get's modified
492  * for each new card.
493  */
494 int saa7164_dvb_register(struct saa7164_tsport *port)
495 {
496         struct saa7164_dev *dev = port->dev;
497         struct saa7164_dvb *dvb = &port->dvb;
498         struct saa7164_i2c *i2c_bus = NULL;
499         int ret;
500
501         dprintk(DBGLVL_DVB, "%s()\n", __func__);
502
503         /* init frontend */
504         switch (dev->board) {
505         case SAA7164_BOARD_HAUPPAUGE_HVR2200:
506         case SAA7164_BOARD_HAUPPAUGE_HVR2200_2:
507         case SAA7164_BOARD_HAUPPAUGE_HVR2200_3:
508                 switch (port->nr) {
509                 case 0:
510                         i2c_bus = &dev->i2c_bus[1];
511
512                         port->dvb.frontend = dvb_attach(tda10048_attach,
513                                 &hauppauge_hvr2200_1_config,
514                                 &i2c_bus->i2c_adap);
515
516                         if (port->dvb.frontend != NULL) {
517                                 dvb_attach(tda18271_attach, port->dvb.frontend,
518                                         0xc0 >> 1, &i2c_bus->i2c_adap,
519                                         &hauppauge_hvr22x0_tuner_config);
520                         }
521
522                         break;
523                 case 1:
524                         i2c_bus = &dev->i2c_bus[2];
525
526                         port->dvb.frontend = dvb_attach(tda10048_attach,
527                                 &hauppauge_hvr2200_2_config,
528                                 &i2c_bus->i2c_adap);
529
530                         if (port->dvb.frontend != NULL) {
531                                 dvb_attach(tda18271_attach, port->dvb.frontend,
532                                         0xc0 >> 1, &i2c_bus->i2c_adap,
533                                         &hauppauge_hvr22x0_tuner_config);
534                         }
535
536                         break;
537                 }
538                 break;
539         case SAA7164_BOARD_HAUPPAUGE_HVR2250:
540         case SAA7164_BOARD_HAUPPAUGE_HVR2250_2:
541                 i2c_bus = &dev->i2c_bus[port->nr + 1];
542
543                 port->dvb.frontend = dvb_attach(s5h1411_attach,
544                         &hauppauge_s5h1411_config,
545                         &i2c_bus->i2c_adap);
546
547                 if (port->dvb.frontend != NULL) {
548                         /* TODO: addr is in the card struct */
549                         dvb_attach(tda18271_attach, port->dvb.frontend,
550                                 0xc0 >> 1, &i2c_bus->i2c_adap,
551                                 &hauppauge_hvr22x0_tuner_config);
552                 }
553
554                 break;
555         default:
556                 printk(KERN_ERR "%s: The frontend isn't supported\n",
557                        dev->name);
558                 break;
559         }
560         if (NULL == dvb->frontend) {
561                 printk(KERN_ERR "%s() Frontend initialization failed\n",
562                        __func__);
563                 return -1;
564         }
565
566         /* Put the analog decoder in standby to keep it quiet */
567
568         /* register everything */
569         ret = dvb_register(port);
570         if (ret < 0) {
571                 if (dvb->frontend->ops.release)
572                         dvb->frontend->ops.release(dvb->frontend);
573                 return ret;
574         }
575
576         return 0;
577 }
578