aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/dvb/frontends/tda827x.c
diff options
context:
space:
mode:
authorMichael Krufky <mkrufky@linuxtv.org>2007-04-27 11:31:14 -0400
committerMauro Carvalho Chehab <mchehab@infradead.org>2007-04-27 14:43:35 -0400
commit8ce47dad8e2b9fcb899a67e6537337fa9c18c1f5 (patch)
tree8482515d25ebc98bf7ac74e47b78f576f23b753f /drivers/media/dvb/frontends/tda827x.c
parent7c7fea669d77048b3013567dfb4c9171d536da05 (diff)
V4L/DVB (5317): Create tda827x dvb tuner module
The patch moves the tda827x dvb tuning code to a separate module Signed-off-by: Michael Krufky <mkrufky@linuxtv.org> Signed-off-by: Hartmut Hackmann <hartmut.hackmann@t-online.de> Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
Diffstat (limited to 'drivers/media/dvb/frontends/tda827x.c')
-rw-r--r--drivers/media/dvb/frontends/tda827x.c476
1 files changed, 476 insertions, 0 deletions
diff --git a/drivers/media/dvb/frontends/tda827x.c b/drivers/media/dvb/frontends/tda827x.c
new file mode 100644
index 000000000000..cff2d1fa4094
--- /dev/null
+++ b/drivers/media/dvb/frontends/tda827x.c
@@ -0,0 +1,476 @@
1/*
2 *
3 * (c) 2005 Hartmut Hackmann
4 * (c) 2007 Michael Krufky
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 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21#include <linux/module.h>
22#include <linux/dvb/frontend.h>
23#include <asm/types.h>
24
25#include "tda827x.h"
26
27static int debug = 0;
28#define dprintk(args...) \
29 do { \
30 if (debug) printk(KERN_DEBUG "tda827x: " args); \
31 } while (0)
32
33struct tda827x_priv {
34 int i2c_addr;
35 struct i2c_adapter *i2c_adap;
36 struct tda827x_config *cfg;
37 u32 frequency;
38 u32 bandwidth;
39};
40
41struct tda827x_data {
42 u32 lomax;
43 u8 spd;
44 u8 bs;
45 u8 bp;
46 u8 cp;
47 u8 gc3;
48 u8 div1p5;
49};
50
51static const struct tda827x_data tda827x_dvbt[] = {
52 { .lomax = 62000000, .spd = 3, .bs = 2, .bp = 0, .cp = 0, .gc3 = 3, .div1p5 = 1},
53 { .lomax = 66000000, .spd = 3, .bs = 3, .bp = 0, .cp = 0, .gc3 = 3, .div1p5 = 1},
54 { .lomax = 76000000, .spd = 3, .bs = 1, .bp = 0, .cp = 0, .gc3 = 3, .div1p5 = 0},
55 { .lomax = 84000000, .spd = 3, .bs = 2, .bp = 0, .cp = 0, .gc3 = 3, .div1p5 = 0},
56 { .lomax = 93000000, .spd = 3, .bs = 2, .bp = 0, .cp = 0, .gc3 = 1, .div1p5 = 0},
57 { .lomax = 98000000, .spd = 3, .bs = 3, .bp = 0, .cp = 0, .gc3 = 1, .div1p5 = 0},
58 { .lomax = 109000000, .spd = 3, .bs = 3, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 0},
59 { .lomax = 123000000, .spd = 2, .bs = 2, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 1},
60 { .lomax = 133000000, .spd = 2, .bs = 3, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 1},
61 { .lomax = 151000000, .spd = 2, .bs = 1, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 0},
62 { .lomax = 154000000, .spd = 2, .bs = 2, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 0},
63 { .lomax = 181000000, .spd = 2, .bs = 2, .bp = 1, .cp = 0, .gc3 = 0, .div1p5 = 0},
64 { .lomax = 185000000, .spd = 2, .bs = 2, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 0},
65 { .lomax = 217000000, .spd = 2, .bs = 3, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 0},
66 { .lomax = 244000000, .spd = 1, .bs = 2, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 1},
67 { .lomax = 265000000, .spd = 1, .bs = 3, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 1},
68 { .lomax = 302000000, .spd = 1, .bs = 1, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 0},
69 { .lomax = 324000000, .spd = 1, .bs = 2, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 0},
70 { .lomax = 370000000, .spd = 1, .bs = 2, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 0},
71 { .lomax = 454000000, .spd = 1, .bs = 3, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 0},
72 { .lomax = 493000000, .spd = 0, .bs = 2, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 1},
73 { .lomax = 530000000, .spd = 0, .bs = 3, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 1},
74 { .lomax = 554000000, .spd = 0, .bs = 1, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 0},
75 { .lomax = 604000000, .spd = 0, .bs = 1, .bp = 4, .cp = 0, .gc3 = 0, .div1p5 = 0},
76 { .lomax = 696000000, .spd = 0, .bs = 2, .bp = 4, .cp = 0, .gc3 = 0, .div1p5 = 0},
77 { .lomax = 740000000, .spd = 0, .bs = 2, .bp = 4, .cp = 1, .gc3 = 0, .div1p5 = 0},
78 { .lomax = 820000000, .spd = 0, .bs = 3, .bp = 4, .cp = 0, .gc3 = 0, .div1p5 = 0},
79 { .lomax = 865000000, .spd = 0, .bs = 3, .bp = 4, .cp = 1, .gc3 = 0, .div1p5 = 0},
80 { .lomax = 0, .spd = 0, .bs = 0, .bp = 0, .cp = 0, .gc3 = 0, .div1p5 = 0}
81};
82
83static int tda827xo_set_params(struct dvb_frontend *fe,
84 struct dvb_frontend_parameters *params)
85{
86 struct tda827x_priv *priv = fe->tuner_priv;
87 u8 buf[14];
88
89 struct i2c_msg msg = { .addr = priv->i2c_addr, .flags = 0,
90 .buf = buf, .len = sizeof(buf) };
91 int i, tuner_freq, if_freq;
92 u32 N;
93
94 switch (params->u.ofdm.bandwidth) {
95 case BANDWIDTH_6_MHZ:
96 if_freq = 4000000;
97 break;
98 case BANDWIDTH_7_MHZ:
99 if_freq = 4500000;
100 break;
101 default: /* 8 MHz or Auto */
102 if_freq = 5000000;
103 break;
104 }
105 tuner_freq = params->frequency + if_freq;
106
107 i = 0;
108 while (tda827x_dvbt[i].lomax < tuner_freq) {
109 if(tda827x_dvbt[i + 1].lomax == 0)
110 break;
111 i++;
112 }
113
114 N = ((tuner_freq + 125000) / 250000) << (tda827x_dvbt[i].spd + 2);
115 buf[0] = 0;
116 buf[1] = (N>>8) | 0x40;
117 buf[2] = N & 0xff;
118 buf[3] = 0;
119 buf[4] = 0x52;
120 buf[5] = (tda827x_dvbt[i].spd << 6) + (tda827x_dvbt[i].div1p5 << 5) +
121 (tda827x_dvbt[i].bs << 3) + tda827x_dvbt[i].bp;
122 buf[6] = (tda827x_dvbt[i].gc3 << 4) + 0x8f;
123 buf[7] = 0xbf;
124 buf[8] = 0x2a;
125 buf[9] = 0x05;
126 buf[10] = 0xff;
127 buf[11] = 0x00;
128 buf[12] = 0x00;
129 buf[13] = 0x40;
130
131 msg.len = 14;
132 if (fe->ops.i2c_gate_ctrl)
133 fe->ops.i2c_gate_ctrl(fe, 1);
134 if (i2c_transfer(priv->i2c_adap, &msg, 1) != 1) {
135 printk("%s: could not write to tuner at addr: 0x%02x\n",
136 __FUNCTION__, priv->i2c_addr << 1);
137 return -EIO;
138 }
139 msleep(500);
140 /* correct CP value */
141 buf[0] = 0x30;
142 buf[1] = 0x50 + tda827x_dvbt[i].cp;
143 msg.len = 2;
144
145 if (fe->ops.i2c_gate_ctrl)
146 fe->ops.i2c_gate_ctrl(fe, 1);
147 i2c_transfer(priv->i2c_adap, &msg, 1);
148
149 priv->frequency = tuner_freq - if_freq; // FIXME
150 priv->bandwidth = (fe->ops.info.type == FE_OFDM) ? params->u.ofdm.bandwidth : 0;
151
152 return 0;
153}
154
155static int tda827xo_sleep(struct dvb_frontend *fe)
156{
157 struct tda827x_priv *priv = fe->tuner_priv;
158 static u8 buf[] = { 0x30, 0xd0 };
159 struct i2c_msg msg = { .addr = priv->i2c_addr, .flags = 0,
160 .buf = buf, .len = sizeof(buf) };
161
162 if (fe->ops.i2c_gate_ctrl)
163 fe->ops.i2c_gate_ctrl(fe, 1);
164 i2c_transfer(priv->i2c_adap, &msg, 1);
165
166 if (priv->cfg && priv->cfg->sleep)
167 priv->cfg->sleep(fe);
168
169 return 0;
170}
171
172/* ------------------------------------------------------------------ */
173
174struct tda827xa_data {
175 u32 lomax;
176 u8 svco;
177 u8 spd;
178 u8 scr;
179 u8 sbs;
180 u8 gc3;
181};
182
183static const struct tda827xa_data tda827xa_dvbt[] = {
184 { .lomax = 56875000, .svco = 3, .spd = 4, .scr = 0, .sbs = 0, .gc3 = 1},
185 { .lomax = 67250000, .svco = 0, .spd = 3, .scr = 0, .sbs = 0, .gc3 = 1},
186 { .lomax = 81250000, .svco = 1, .spd = 3, .scr = 0, .sbs = 0, .gc3 = 1},
187 { .lomax = 97500000, .svco = 2, .spd = 3, .scr = 0, .sbs = 0, .gc3 = 1},
188 { .lomax = 113750000, .svco = 3, .spd = 3, .scr = 0, .sbs = 1, .gc3 = 1},
189 { .lomax = 134500000, .svco = 0, .spd = 2, .scr = 0, .sbs = 1, .gc3 = 1},
190 { .lomax = 154000000, .svco = 1, .spd = 2, .scr = 0, .sbs = 1, .gc3 = 1},
191 { .lomax = 162500000, .svco = 1, .spd = 2, .scr = 0, .sbs = 1, .gc3 = 1},
192 { .lomax = 183000000, .svco = 2, .spd = 2, .scr = 0, .sbs = 1, .gc3 = 1},
193 { .lomax = 195000000, .svco = 2, .spd = 2, .scr = 0, .sbs = 2, .gc3 = 1},
194 { .lomax = 227500000, .svco = 3, .spd = 2, .scr = 0, .sbs = 2, .gc3 = 1},
195 { .lomax = 269000000, .svco = 0, .spd = 1, .scr = 0, .sbs = 2, .gc3 = 1},
196 { .lomax = 290000000, .svco = 1, .spd = 1, .scr = 0, .sbs = 2, .gc3 = 1},
197 { .lomax = 325000000, .svco = 1, .spd = 1, .scr = 0, .sbs = 3, .gc3 = 1},
198 { .lomax = 390000000, .svco = 2, .spd = 1, .scr = 0, .sbs = 3, .gc3 = 1},
199 { .lomax = 455000000, .svco = 3, .spd = 1, .scr = 0, .sbs = 3, .gc3 = 1},
200 { .lomax = 520000000, .svco = 0, .spd = 0, .scr = 0, .sbs = 3, .gc3 = 1},
201 { .lomax = 538000000, .svco = 0, .spd = 0, .scr = 1, .sbs = 3, .gc3 = 1},
202 { .lomax = 550000000, .svco = 1, .spd = 0, .scr = 0, .sbs = 3, .gc3 = 1},
203 { .lomax = 620000000, .svco = 1, .spd = 0, .scr = 0, .sbs = 4, .gc3 = 0},
204 { .lomax = 650000000, .svco = 1, .spd = 0, .scr = 1, .sbs = 4, .gc3 = 0},
205 { .lomax = 700000000, .svco = 2, .spd = 0, .scr = 0, .sbs = 4, .gc3 = 0},
206 { .lomax = 780000000, .svco = 2, .spd = 0, .scr = 1, .sbs = 4, .gc3 = 0},
207 { .lomax = 820000000, .svco = 3, .spd = 0, .scr = 0, .sbs = 4, .gc3 = 0},
208 { .lomax = 870000000, .svco = 3, .spd = 0, .scr = 1, .sbs = 4, .gc3 = 0},
209 { .lomax = 911000000, .svco = 3, .spd = 0, .scr = 2, .sbs = 4, .gc3 = 0},
210 { .lomax = 0, .svco = 0, .spd = 0, .scr = 0, .sbs = 0, .gc3 = 0}
211};
212
213static int tda827xa_set_params(struct dvb_frontend *fe,
214 struct dvb_frontend_parameters *params)
215{
216 struct tda827x_priv *priv = fe->tuner_priv;
217 u8 buf[10];
218
219 struct i2c_msg msg = { .addr = priv->i2c_addr, .flags = 0,
220 .buf = buf, .len = sizeof(buf) };
221
222 int i, tuner_freq, if_freq;
223 u32 N;
224
225 if (priv->cfg && priv->cfg->lna_gain)
226 priv->cfg->lna_gain(fe, 1);
227 msleep(20);
228
229 switch (params->u.ofdm.bandwidth) {
230 case BANDWIDTH_6_MHZ:
231 if_freq = 4000000;
232 break;
233 case BANDWIDTH_7_MHZ:
234 if_freq = 4500000;
235 break;
236 default: /* 8 MHz or Auto */
237 if_freq = 5000000;
238 break;
239 }
240 tuner_freq = params->frequency + if_freq;
241
242 i = 0;
243 while (tda827xa_dvbt[i].lomax < tuner_freq) {
244 if(tda827xa_dvbt[i + 1].lomax == 0)
245 break;
246 i++;
247 }
248
249 N = ((tuner_freq + 31250) / 62500) << tda827xa_dvbt[i].spd;
250 buf[0] = 0; // subaddress
251 buf[1] = N >> 8;
252 buf[2] = N & 0xff;
253 buf[3] = 0;
254 buf[4] = 0x16;
255 buf[5] = (tda827xa_dvbt[i].spd << 5) + (tda827xa_dvbt[i].svco << 3) +
256 tda827xa_dvbt[i].sbs;
257 buf[6] = 0x4b + (tda827xa_dvbt[i].gc3 << 4);
258 buf[7] = 0x1c;
259 buf[8] = 0x06;
260 buf[9] = 0x24;
261 buf[10] = 0x00;
262 msg.len = 11;
263 if (fe->ops.i2c_gate_ctrl)
264 fe->ops.i2c_gate_ctrl(fe, 1);
265 if (i2c_transfer(priv->i2c_adap, &msg, 1) != 1) {
266 printk("%s: could not write to tuner at addr: 0x%02x\n",
267 __FUNCTION__, priv->i2c_addr << 1);
268 return -EIO;
269 }
270 buf[0] = 0x90;
271 buf[1] = 0xff;
272 buf[2] = 0x60;
273 buf[3] = 0x00;
274 buf[4] = 0x59; // lpsel, for 6MHz + 2
275 msg.len = 5;
276 if (fe->ops.i2c_gate_ctrl)
277 fe->ops.i2c_gate_ctrl(fe, 1);
278 i2c_transfer(priv->i2c_adap, &msg, 1);
279
280 buf[0] = 0xa0;
281 buf[1] = 0x40;
282 msg.len = 2;
283 if (fe->ops.i2c_gate_ctrl)
284 fe->ops.i2c_gate_ctrl(fe, 1);
285 i2c_transfer(priv->i2c_adap, &msg, 1);
286
287 msleep(11);
288 msg.flags = I2C_M_RD;
289 if (fe->ops.i2c_gate_ctrl)
290 fe->ops.i2c_gate_ctrl(fe, 1);
291 i2c_transfer(priv->i2c_adap, &msg, 1);
292 msg.flags = 0;
293
294 buf[1] >>= 4;
295 dprintk("tda8275a AGC2 gain is: %d\n", buf[1]);
296 if ((buf[1]) < 2) {
297 if (priv->cfg && priv->cfg->lna_gain)
298 priv->cfg->lna_gain(fe, 0);
299 buf[0] = 0x60;
300 buf[1] = 0x0c;
301 if (fe->ops.i2c_gate_ctrl)
302 fe->ops.i2c_gate_ctrl(fe, 1);
303 i2c_transfer(priv->i2c_adap, &msg, 1);
304 }
305
306 buf[0] = 0xc0;
307 buf[1] = 0x99; // lpsel, for 6MHz + 2
308 if (fe->ops.i2c_gate_ctrl)
309 fe->ops.i2c_gate_ctrl(fe, 1);
310 i2c_transfer(priv->i2c_adap, &msg, 1);
311
312 buf[0] = 0x60;
313 buf[1] = 0x3c;
314 if (fe->ops.i2c_gate_ctrl)
315 fe->ops.i2c_gate_ctrl(fe, 1);
316 i2c_transfer(priv->i2c_adap, &msg, 1);
317
318 /* correct CP value */
319 buf[0] = 0x30;
320 buf[1] = 0x10 + tda827xa_dvbt[i].scr;
321 if (fe->ops.i2c_gate_ctrl)
322 fe->ops.i2c_gate_ctrl(fe, 1);
323 i2c_transfer(priv->i2c_adap, &msg, 1);
324
325 msleep(163);
326 buf[0] = 0xc0;
327 buf[1] = 0x39; // lpsel, for 6MHz + 2
328 if (fe->ops.i2c_gate_ctrl)
329 fe->ops.i2c_gate_ctrl(fe, 1);
330 i2c_transfer(priv->i2c_adap, &msg, 1);
331
332 msleep(3);
333 /* freeze AGC1 */
334 buf[0] = 0x50;
335 buf[1] = 0x4f + (tda827xa_dvbt[i].gc3 << 4);
336 if (fe->ops.i2c_gate_ctrl)
337 fe->ops.i2c_gate_ctrl(fe, 1);
338 i2c_transfer(priv->i2c_adap, &msg, 1);
339
340 priv->frequency = tuner_freq - if_freq; // FIXME
341 priv->bandwidth = (fe->ops.info.type == FE_OFDM) ? params->u.ofdm.bandwidth : 0;
342
343 return 0;
344}
345
346static int tda827xa_sleep(struct dvb_frontend *fe)
347{
348 struct tda827x_priv *priv = fe->tuner_priv;
349 static u8 buf[] = { 0x30, 0x90 };
350 struct i2c_msg msg = { .addr = priv->i2c_addr, .flags = 0,
351 .buf = buf, .len = sizeof(buf) };
352
353 if (fe->ops.i2c_gate_ctrl)
354 fe->ops.i2c_gate_ctrl(fe, 1);
355
356 i2c_transfer(priv->i2c_adap, &msg, 1);
357
358 if (fe->ops.i2c_gate_ctrl)
359 fe->ops.i2c_gate_ctrl(fe, 0);
360
361 if (priv->cfg && priv->cfg->sleep)
362 priv->cfg->sleep(fe);
363
364 return 0;
365}
366
367static int tda827x_release(struct dvb_frontend *fe)
368{
369 kfree(fe->tuner_priv);
370 fe->tuner_priv = NULL;
371 return 0;
372}
373
374static int tda827x_get_frequency(struct dvb_frontend *fe, u32 *frequency)
375{
376 struct tda827x_priv *priv = fe->tuner_priv;
377 *frequency = priv->frequency;
378 return 0;
379}
380
381static int tda827x_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
382{
383 struct tda827x_priv *priv = fe->tuner_priv;
384 *bandwidth = priv->bandwidth;
385 return 0;
386}
387
388static int tda827x_init(struct dvb_frontend *fe)
389{
390 struct tda827x_priv *priv = fe->tuner_priv;
391
392 if (priv->cfg && priv->cfg->init)
393 priv->cfg->init(fe);
394
395 return 0;
396}
397
398
399static struct dvb_tuner_ops tda827xo_tuner_ops = {
400 .info = {
401 .name = "Philips TDA827X",
402 },
403 .release = tda827x_release,
404 .init = tda827x_init,
405 .sleep = tda827xo_sleep,
406 .set_params = tda827xo_set_params,
407 .get_frequency = tda827x_get_frequency,
408 .get_bandwidth = tda827x_get_bandwidth,
409};
410
411static struct dvb_tuner_ops tda827xa_tuner_ops = {
412 .info = {
413 .name = "Philips TDA827XA",
414 },
415 .release = tda827x_release,
416 .init = tda827x_init,
417 .sleep = tda827xa_sleep,
418 .set_params = tda827xa_set_params,
419 .get_frequency = tda827x_get_frequency,
420 .get_bandwidth = tda827x_get_bandwidth,
421};
422
423struct dvb_frontend *tda827x_attach(struct dvb_frontend *fe, int addr,
424 struct i2c_adapter *i2c,
425 struct tda827x_config *cfg)
426{
427 struct tda827x_priv *priv = NULL;
428 u8 data;
429 struct i2c_msg msg = { .addr = addr, .flags = I2C_M_RD,
430 .buf = &data, .len = 1 };
431 dprintk("%s:\n", __FUNCTION__);
432
433 if (fe->ops.i2c_gate_ctrl)
434 fe->ops.i2c_gate_ctrl(fe, 1);
435
436 if (i2c_transfer(i2c, &msg, 1) != 1) {
437 printk("%s: could not read from tuner at addr: 0x%02x\n",
438 __FUNCTION__, addr << 1);
439 return NULL;
440 }
441
442 priv = kzalloc(sizeof(struct tda827x_priv), GFP_KERNEL);
443 if (priv == NULL)
444 return NULL;
445
446 priv->i2c_addr = addr;
447 priv->i2c_adap = i2c;
448 priv->cfg = cfg;
449 if ((data & 0x3c) == 0) {
450 dprintk("tda827x tuner found\n");
451 memcpy(&fe->ops.tuner_ops, &tda827xo_tuner_ops, sizeof(struct dvb_tuner_ops));
452 } else {
453 dprintk("tda827xa tuner found\n");
454 memcpy(&fe->ops.tuner_ops, &tda827xa_tuner_ops, sizeof(struct dvb_tuner_ops));
455 }
456 fe->tuner_priv = priv;
457
458 return fe;
459}
460EXPORT_SYMBOL(tda827x_attach);
461
462module_param(debug, int, 0644);
463MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off).");
464
465MODULE_DESCRIPTION("DVB TDA827x driver");
466MODULE_AUTHOR("Hartmut Hackmann");
467MODULE_AUTHOR("Michael Krufky");
468MODULE_LICENSE("GPL");
469
470/*
471 * Overrides for Emacs so that we follow Linus's tabbing style.
472 * ---------------------------------------------------------------------------
473 * Local variables:
474 * c-basic-offset: 8
475 * End:
476 */