linux/drivers/media/dvb/frontends/lgdt3305.c
<<
>>
Prefs
   1/*
   2 *    Support for LG Electronics LGDT3304 and LGDT3305 - VSB/QAM
   3 *
   4 *    Copyright (C) 2008, 2009, 2010 Michael Krufky <mkrufky@linuxtv.org>
   5 *
   6 *    LGDT3304 support by Jarod Wilson <jarod@redhat.com>
   7 *
   8 *    This program is free software; you can redistribute it and/or modify
   9 *    it under the terms of the GNU General Public License as published by
  10 *    the Free Software Foundation; either version 2 of the License, or
  11 *    (at your option) any later version.
  12 *
  13 *    This program is distributed in the hope that it will be useful,
  14 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 *    GNU General Public License for more details.
  17 *
  18 *    You should have received a copy of the GNU General Public License
  19 *    along with this program; if not, write to the Free Software
  20 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21 *
  22 */
  23
  24#include <asm/div64.h>
  25#include <linux/dvb/frontend.h>
  26#include <linux/slab.h>
  27#include "dvb_math.h"
  28#include "lgdt3305.h"
  29
  30static int debug;
  31module_param(debug, int, 0644);
  32MODULE_PARM_DESC(debug, "set debug level (info=1, reg=2 (or-able))");
  33
  34#define DBG_INFO 1
  35#define DBG_REG  2
  36
  37#define lg_printk(kern, fmt, arg...)                                    \
  38        printk(kern "%s: " fmt, __func__, ##arg)
  39
  40#define lg_info(fmt, arg...)    printk(KERN_INFO "lgdt3305: " fmt, ##arg)
  41#define lg_warn(fmt, arg...)    lg_printk(KERN_WARNING,       fmt, ##arg)
  42#define lg_err(fmt, arg...)     lg_printk(KERN_ERR,           fmt, ##arg)
  43#define lg_dbg(fmt, arg...) if (debug & DBG_INFO)                       \
  44                                lg_printk(KERN_DEBUG,         fmt, ##arg)
  45#define lg_reg(fmt, arg...) if (debug & DBG_REG)                        \
  46                                lg_printk(KERN_DEBUG,         fmt, ##arg)
  47
  48#define lg_fail(ret)                                                    \
  49({                                                                      \
  50        int __ret;                                                      \
  51        __ret = (ret < 0);                                              \
  52        if (__ret)                                                      \
  53                lg_err("error %d on line %d\n", ret, __LINE__);         \
  54        __ret;                                                          \
  55})
  56
  57struct lgdt3305_state {
  58        struct i2c_adapter *i2c_adap;
  59        const struct lgdt3305_config *cfg;
  60
  61        struct dvb_frontend frontend;
  62
  63        fe_modulation_t current_modulation;
  64        u32 current_frequency;
  65        u32 snr;
  66};
  67
  68/* ------------------------------------------------------------------------ */
  69
  70/* FIXME: verify & document the LGDT3304 registers */
  71
  72#define LGDT3305_GEN_CTRL_1                   0x0000
  73#define LGDT3305_GEN_CTRL_2                   0x0001
  74#define LGDT3305_GEN_CTRL_3                   0x0002
  75#define LGDT3305_GEN_STATUS                   0x0003
  76#define LGDT3305_GEN_CONTROL                  0x0007
  77#define LGDT3305_GEN_CTRL_4                   0x000a
  78#define LGDT3305_DGTL_AGC_REF_1               0x0012
  79#define LGDT3305_DGTL_AGC_REF_2               0x0013
  80#define LGDT3305_CR_CTR_FREQ_1                0x0106
  81#define LGDT3305_CR_CTR_FREQ_2                0x0107
  82#define LGDT3305_CR_CTR_FREQ_3                0x0108
  83#define LGDT3305_CR_CTR_FREQ_4                0x0109
  84#define LGDT3305_CR_MSE_1                     0x011b
  85#define LGDT3305_CR_MSE_2                     0x011c
  86#define LGDT3305_CR_LOCK_STATUS               0x011d
  87#define LGDT3305_CR_CTRL_7                    0x0126
  88#define LGDT3305_AGC_POWER_REF_1              0x0300
  89#define LGDT3305_AGC_POWER_REF_2              0x0301
  90#define LGDT3305_AGC_DELAY_PT_1               0x0302
  91#define LGDT3305_AGC_DELAY_PT_2               0x0303
  92#define LGDT3305_RFAGC_LOOP_FLTR_BW_1         0x0306
  93#define LGDT3305_RFAGC_LOOP_FLTR_BW_2         0x0307
  94#define LGDT3305_IFBW_1                       0x0308
  95#define LGDT3305_IFBW_2                       0x0309
  96#define LGDT3305_AGC_CTRL_1                   0x030c
  97#define LGDT3305_AGC_CTRL_4                   0x0314
  98#define LGDT3305_EQ_MSE_1                     0x0413
  99#define LGDT3305_EQ_MSE_2                     0x0414
 100#define LGDT3305_EQ_MSE_3                     0x0415
 101#define LGDT3305_PT_MSE_1                     0x0417
 102#define LGDT3305_PT_MSE_2                     0x0418
 103#define LGDT3305_PT_MSE_3                     0x0419
 104#define LGDT3305_FEC_BLOCK_CTRL               0x0504
 105#define LGDT3305_FEC_LOCK_STATUS              0x050a
 106#define LGDT3305_FEC_PKT_ERR_1                0x050c
 107#define LGDT3305_FEC_PKT_ERR_2                0x050d
 108#define LGDT3305_TP_CTRL_1                    0x050e
 109#define LGDT3305_BERT_PERIOD                  0x0801
 110#define LGDT3305_BERT_ERROR_COUNT_1           0x080a
 111#define LGDT3305_BERT_ERROR_COUNT_2           0x080b
 112#define LGDT3305_BERT_ERROR_COUNT_3           0x080c
 113#define LGDT3305_BERT_ERROR_COUNT_4           0x080d
 114
 115static int lgdt3305_write_reg(struct lgdt3305_state *state, u16 reg, u8 val)
 116{
 117        int ret;
 118        u8 buf[] = { reg >> 8, reg & 0xff, val };
 119        struct i2c_msg msg = {
 120                .addr = state->cfg->i2c_addr, .flags = 0,
 121                .buf = buf, .len = 3,
 122        };
 123
 124        lg_reg("reg: 0x%04x, val: 0x%02x\n", reg, val);
 125
 126        ret = i2c_transfer(state->i2c_adap, &msg, 1);
 127
 128        if (ret != 1) {
 129                lg_err("error (addr %02x %02x <- %02x, err = %i)\n",
 130                       msg.buf[0], msg.buf[1], msg.buf[2], ret);
 131                if (ret < 0)
 132                        return ret;
 133                else
 134                        return -EREMOTEIO;
 135        }
 136        return 0;
 137}
 138
 139static int lgdt3305_read_reg(struct lgdt3305_state *state, u16 reg, u8 *val)
 140{
 141        int ret;
 142        u8 reg_buf[] = { reg >> 8, reg & 0xff };
 143        struct i2c_msg msg[] = {
 144                { .addr = state->cfg->i2c_addr,
 145                  .flags = 0, .buf = reg_buf, .len = 2 },
 146                { .addr = state->cfg->i2c_addr,
 147                  .flags = I2C_M_RD, .buf = val, .len = 1 },
 148        };
 149
 150        lg_reg("reg: 0x%04x\n", reg);
 151
 152        ret = i2c_transfer(state->i2c_adap, msg, 2);
 153
 154        if (ret != 2) {
 155                lg_err("error (addr %02x reg %04x error (ret == %i)\n",
 156                       state->cfg->i2c_addr, reg, ret);
 157                if (ret < 0)
 158                        return ret;
 159                else
 160                        return -EREMOTEIO;
 161        }
 162        return 0;
 163}
 164
 165#define read_reg(state, reg)                                            \
 166({                                                                      \
 167        u8 __val;                                                       \
 168        int ret = lgdt3305_read_reg(state, reg, &__val);                \
 169        if (lg_fail(ret))                                               \
 170                __val = 0;                                              \
 171        __val;                                                          \
 172})
 173
 174static int lgdt3305_set_reg_bit(struct lgdt3305_state *state,
 175                                u16 reg, int bit, int onoff)
 176{
 177        u8 val;
 178        int ret;
 179
 180        lg_reg("reg: 0x%04x, bit: %d, level: %d\n", reg, bit, onoff);
 181
 182        ret = lgdt3305_read_reg(state, reg, &val);
 183        if (lg_fail(ret))
 184                goto fail;
 185
 186        val &= ~(1 << bit);
 187        val |= (onoff & 1) << bit;
 188
 189        ret = lgdt3305_write_reg(state, reg, val);
 190fail:
 191        return ret;
 192}
 193
 194struct lgdt3305_reg {
 195        u16 reg;
 196        u8 val;
 197};
 198
 199static int lgdt3305_write_regs(struct lgdt3305_state *state,
 200                               struct lgdt3305_reg *regs, int len)
 201{
 202        int i, ret;
 203
 204        lg_reg("writing %d registers...\n", len);
 205
 206        for (i = 0; i < len - 1; i++) {
 207                ret = lgdt3305_write_reg(state, regs[i].reg, regs[i].val);
 208                if (lg_fail(ret))
 209                        return ret;
 210        }
 211        return 0;
 212}
 213
 214/* ------------------------------------------------------------------------ */
 215
 216static int lgdt3305_soft_reset(struct lgdt3305_state *state)
 217{
 218        int ret;
 219
 220        lg_dbg("\n");
 221
 222        ret = lgdt3305_set_reg_bit(state, LGDT3305_GEN_CTRL_3, 0, 0);
 223        if (lg_fail(ret))
 224                goto fail;
 225
 226        msleep(20);
 227        ret = lgdt3305_set_reg_bit(state, LGDT3305_GEN_CTRL_3, 0, 1);
 228fail:
 229        return ret;
 230}
 231
 232static inline int lgdt3305_mpeg_mode(struct lgdt3305_state *state,
 233                                     enum lgdt3305_mpeg_mode mode)
 234{
 235        lg_dbg("(%d)\n", mode);
 236        return lgdt3305_set_reg_bit(state, LGDT3305_TP_CTRL_1, 5, mode);
 237}
 238
 239static int lgdt3305_mpeg_mode_polarity(struct lgdt3305_state *state,
 240                                       enum lgdt3305_tp_clock_edge edge,
 241                                       enum lgdt3305_tp_valid_polarity valid)
 242{
 243        u8 val;
 244        int ret;
 245
 246        lg_dbg("edge = %d, valid = %d\n", edge, valid);
 247
 248        ret = lgdt3305_read_reg(state, LGDT3305_TP_CTRL_1, &val);
 249        if (lg_fail(ret))
 250                goto fail;
 251
 252        val &= ~0x09;
 253
 254        if (edge)
 255                val |= 0x08;
 256        if (valid)
 257                val |= 0x01;
 258
 259        ret = lgdt3305_write_reg(state, LGDT3305_TP_CTRL_1, val);
 260        if (lg_fail(ret))
 261                goto fail;
 262
 263        ret = lgdt3305_soft_reset(state);
 264fail:
 265        return ret;
 266}
 267
 268static int lgdt3305_set_modulation(struct lgdt3305_state *state,
 269                                   struct dvb_frontend_parameters *param)
 270{
 271        u8 opermode;
 272        int ret;
 273
 274        lg_dbg("\n");
 275
 276        ret = lgdt3305_read_reg(state, LGDT3305_GEN_CTRL_1, &opermode);
 277        if (lg_fail(ret))
 278                goto fail;
 279
 280        opermode &= ~0x03;
 281
 282        switch (param->u.vsb.modulation) {
 283        case VSB_8:
 284                opermode |= 0x03;
 285                break;
 286        case QAM_64:
 287                opermode |= 0x00;
 288                break;
 289        case QAM_256:
 290                opermode |= 0x01;
 291                break;
 292        default:
 293                return -EINVAL;
 294        }
 295        ret = lgdt3305_write_reg(state, LGDT3305_GEN_CTRL_1, opermode);
 296fail:
 297        return ret;
 298}
 299
 300static int lgdt3305_set_filter_extension(struct lgdt3305_state *state,
 301                                         struct dvb_frontend_parameters *param)
 302{
 303        int val;
 304
 305        switch (param->u.vsb.modulation) {
 306        case VSB_8:
 307                val = 0;
 308                break;
 309        case QAM_64:
 310        case QAM_256:
 311                val = 1;
 312                break;
 313        default:
 314                return -EINVAL;
 315        }
 316        lg_dbg("val = %d\n", val);
 317
 318        return lgdt3305_set_reg_bit(state, 0x043f, 2, val);
 319}
 320
 321/* ------------------------------------------------------------------------ */
 322
 323static int lgdt3305_passband_digital_agc(struct lgdt3305_state *state,
 324                                         struct dvb_frontend_parameters *param)
 325{
 326        u16 agc_ref;
 327
 328        switch (param->u.vsb.modulation) {
 329        case VSB_8:
 330                agc_ref = 0x32c4;
 331                break;
 332        case QAM_64:
 333                agc_ref = 0x2a00;
 334                break;
 335        case QAM_256:
 336                agc_ref = 0x2a80;
 337                break;
 338        default:
 339                return -EINVAL;
 340        }
 341
 342        lg_dbg("agc ref: 0x%04x\n", agc_ref);
 343
 344        lgdt3305_write_reg(state, LGDT3305_DGTL_AGC_REF_1, agc_ref >> 8);
 345        lgdt3305_write_reg(state, LGDT3305_DGTL_AGC_REF_2, agc_ref & 0xff);
 346
 347        return 0;
 348}
 349
 350static int lgdt3305_rfagc_loop(struct lgdt3305_state *state,
 351                               struct dvb_frontend_parameters *param)
 352{
 353        u16 ifbw, rfbw, agcdelay;
 354
 355        switch (param->u.vsb.modulation) {
 356        case VSB_8:
 357                agcdelay = 0x04c0;
 358                rfbw     = 0x8000;
 359                ifbw     = 0x8000;
 360                break;
 361        case QAM_64:
 362        case QAM_256:
 363                agcdelay = 0x046b;
 364                rfbw     = 0x8889;
 365                /* FIXME: investigate optimal ifbw & rfbw values for the
 366                 *        DT3304 and re-write this switch..case block */
 367                if (state->cfg->demod_chip == LGDT3304)
 368                        ifbw = 0x6666;
 369                else /* (state->cfg->demod_chip == LGDT3305) */
 370                        ifbw = 0x8888;
 371                break;
 372        default:
 373                return -EINVAL;
 374        }
 375
 376        if (state->cfg->rf_agc_loop) {
 377                lg_dbg("agcdelay: 0x%04x, rfbw: 0x%04x\n", agcdelay, rfbw);
 378
 379                /* rf agc loop filter bandwidth */
 380                lgdt3305_write_reg(state, LGDT3305_AGC_DELAY_PT_1,
 381                                   agcdelay >> 8);
 382                lgdt3305_write_reg(state, LGDT3305_AGC_DELAY_PT_2,
 383                                   agcdelay & 0xff);
 384
 385                lgdt3305_write_reg(state, LGDT3305_RFAGC_LOOP_FLTR_BW_1,
 386                                   rfbw >> 8);
 387                lgdt3305_write_reg(state, LGDT3305_RFAGC_LOOP_FLTR_BW_2,
 388                                   rfbw & 0xff);
 389        } else {
 390                lg_dbg("ifbw: 0x%04x\n", ifbw);
 391
 392                /* if agc loop filter bandwidth */
 393                lgdt3305_write_reg(state, LGDT3305_IFBW_1, ifbw >> 8);
 394                lgdt3305_write_reg(state, LGDT3305_IFBW_2, ifbw & 0xff);
 395        }
 396
 397        return 0;
 398}
 399
 400static int lgdt3305_agc_setup(struct lgdt3305_state *state,
 401                              struct dvb_frontend_parameters *param)
 402{
 403        int lockdten, acqen;
 404
 405        switch (param->u.vsb.modulation) {
 406        case VSB_8:
 407                lockdten = 0;
 408                acqen = 0;
 409                break;
 410        case QAM_64:
 411        case QAM_256:
 412                lockdten = 1;
 413                acqen = 1;
 414                break;
 415        default:
 416                return -EINVAL;
 417        }
 418
 419        lg_dbg("lockdten = %d, acqen = %d\n", lockdten, acqen);
 420
 421        /* control agc function */
 422        switch (state->cfg->demod_chip) {
 423        case LGDT3304:
 424                lgdt3305_write_reg(state, 0x0314, 0xe1 | lockdten << 1);
 425                lgdt3305_set_reg_bit(state, 0x030e, 2, acqen);
 426                break;
 427        case LGDT3305:
 428                lgdt3305_write_reg(state, LGDT3305_AGC_CTRL_4, 0xe1 | lockdten << 1);
 429                lgdt3305_set_reg_bit(state, LGDT3305_AGC_CTRL_1, 2, acqen);
 430                break;
 431        default:
 432                return -EINVAL;
 433        }
 434
 435        return lgdt3305_rfagc_loop(state, param);
 436}
 437
 438static int lgdt3305_set_agc_power_ref(struct lgdt3305_state *state,
 439                                      struct dvb_frontend_parameters *param)
 440{
 441        u16 usref = 0;
 442
 443        switch (param->u.vsb.modulation) {
 444        case VSB_8:
 445                if (state->cfg->usref_8vsb)
 446                        usref = state->cfg->usref_8vsb;
 447                break;
 448        case QAM_64:
 449                if (state->cfg->usref_qam64)
 450                        usref = state->cfg->usref_qam64;
 451                break;
 452        case QAM_256:
 453                if (state->cfg->usref_qam256)
 454                        usref = state->cfg->usref_qam256;
 455                break;
 456        default:
 457                return -EINVAL;
 458        }
 459
 460        if (usref) {
 461                lg_dbg("set manual mode: 0x%04x\n", usref);
 462
 463                lgdt3305_set_reg_bit(state, LGDT3305_AGC_CTRL_1, 3, 1);
 464
 465                lgdt3305_write_reg(state, LGDT3305_AGC_POWER_REF_1,
 466                                   0xff & (usref >> 8));
 467                lgdt3305_write_reg(state, LGDT3305_AGC_POWER_REF_2,
 468                                   0xff & (usref >> 0));
 469        }
 470        return 0;
 471}
 472
 473/* ------------------------------------------------------------------------ */
 474
 475static int lgdt3305_spectral_inversion(struct lgdt3305_state *state,
 476                                       struct dvb_frontend_parameters *param,
 477                                       int inversion)
 478{
 479        int ret;
 480
 481        lg_dbg("(%d)\n", inversion);
 482
 483        switch (param->u.vsb.modulation) {
 484        case VSB_8:
 485                ret = lgdt3305_write_reg(state, LGDT3305_CR_CTRL_7,
 486                                         inversion ? 0xf9 : 0x79);
 487                break;
 488        case QAM_64:
 489        case QAM_256:
 490                ret = lgdt3305_write_reg(state, LGDT3305_FEC_BLOCK_CTRL,
 491                                         inversion ? 0xfd : 0xff);
 492                break;
 493        default:
 494                ret = -EINVAL;
 495        }
 496        return ret;
 497}
 498
 499static int lgdt3305_set_if(struct lgdt3305_state *state,
 500                           struct dvb_frontend_parameters *param)
 501{
 502        u16 if_freq_khz;
 503        u8 nco1, nco2, nco3, nco4;
 504        u64 nco;
 505
 506        switch (param->u.vsb.modulation) {
 507        case VSB_8:
 508                if_freq_khz = state->cfg->vsb_if_khz;
 509                break;
 510        case QAM_64:
 511        case QAM_256:
 512                if_freq_khz = state->cfg->qam_if_khz;
 513                break;
 514        default:
 515                return -EINVAL;
 516        }
 517
 518        nco = if_freq_khz / 10;
 519
 520        switch (param->u.vsb.modulation) {
 521        case VSB_8:
 522                nco <<= 24;
 523                do_div(nco, 625);
 524                break;
 525        case QAM_64:
 526        case QAM_256:
 527                nco <<= 28;
 528                do_div(nco, 625);
 529                break;
 530        default:
 531                return -EINVAL;
 532        }
 533
 534        nco1 = (nco >> 24) & 0x3f;
 535        nco1 |= 0x40;
 536        nco2 = (nco >> 16) & 0xff;
 537        nco3 = (nco >> 8) & 0xff;
 538        nco4 = nco & 0xff;
 539
 540        lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_1, nco1);
 541        lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_2, nco2);
 542        lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_3, nco3);
 543        lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_4, nco4);
 544
 545        lg_dbg("%d KHz -> [%02x%02x%02x%02x]\n",
 546               if_freq_khz, nco1, nco2, nco3, nco4);
 547
 548        return 0;
 549}
 550
 551/* ------------------------------------------------------------------------ */
 552
 553static int lgdt3305_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
 554{
 555        struct lgdt3305_state *state = fe->demodulator_priv;
 556
 557        if (state->cfg->deny_i2c_rptr)
 558                return 0;
 559
 560        lg_dbg("(%d)\n", enable);
 561
 562        return lgdt3305_set_reg_bit(state, LGDT3305_GEN_CTRL_2, 5,
 563                                    enable ? 0 : 1);
 564}
 565
 566static int lgdt3305_sleep(struct dvb_frontend *fe)
 567{
 568        struct lgdt3305_state *state = fe->demodulator_priv;
 569        u8 gen_ctrl_3, gen_ctrl_4;
 570
 571        lg_dbg("\n");
 572
 573        gen_ctrl_3 = read_reg(state, LGDT3305_GEN_CTRL_3);
 574        gen_ctrl_4 = read_reg(state, LGDT3305_GEN_CTRL_4);
 575
 576        /* hold in software reset while sleeping */
 577        gen_ctrl_3 &= ~0x01;
 578        /* tristate the IF-AGC pin */
 579        gen_ctrl_3 |=  0x02;
 580        /* tristate the RF-AGC pin */
 581        gen_ctrl_3 |=  0x04;
 582
 583        /* disable vsb/qam module */
 584        gen_ctrl_4 &= ~0x01;
 585        /* disable adc module */
 586        gen_ctrl_4 &= ~0x02;
 587
 588        lgdt3305_write_reg(state, LGDT3305_GEN_CTRL_3, gen_ctrl_3);
 589        lgdt3305_write_reg(state, LGDT3305_GEN_CTRL_4, gen_ctrl_4);
 590
 591        return 0;
 592}
 593
 594static int lgdt3305_init(struct dvb_frontend *fe)
 595{
 596        struct lgdt3305_state *state = fe->demodulator_priv;
 597        int ret;
 598
 599        static struct lgdt3305_reg lgdt3304_init_data[] = {
 600                { .reg = LGDT3305_GEN_CTRL_1,           .val = 0x03, },
 601                { .reg = 0x000d,                        .val = 0x02, },
 602                { .reg = 0x000e,                        .val = 0x02, },
 603                { .reg = LGDT3305_DGTL_AGC_REF_1,       .val = 0x32, },
 604                { .reg = LGDT3305_DGTL_AGC_REF_2,       .val = 0xc4, },
 605                { .reg = LGDT3305_CR_CTR_FREQ_1,        .val = 0x00, },
 606                { .reg = LGDT3305_CR_CTR_FREQ_2,        .val = 0x00, },
 607                { .reg = LGDT3305_CR_CTR_FREQ_3,        .val = 0x00, },
 608                { .reg = LGDT3305_CR_CTR_FREQ_4,        .val = 0x00, },
 609                { .reg = LGDT3305_CR_CTRL_7,            .val = 0xf9, },
 610                { .reg = 0x0112,                        .val = 0x17, },
 611                { .reg = 0x0113,                        .val = 0x15, },
 612                { .reg = 0x0114,                        .val = 0x18, },
 613                { .reg = 0x0115,                        .val = 0xff, },
 614                { .reg = 0x0116,                        .val = 0x3c, },
 615                { .reg = 0x0214,                        .val = 0x67, },
 616                { .reg = 0x0424,                        .val = 0x8d, },
 617                { .reg = 0x0427,                        .val = 0x12, },
 618                { .reg = 0x0428,                        .val = 0x4f, },
 619                { .reg = LGDT3305_IFBW_1,               .val = 0x80, },
 620                { .reg = LGDT3305_IFBW_2,               .val = 0x00, },
 621                { .reg = 0x030a,                        .val = 0x08, },
 622                { .reg = 0x030b,                        .val = 0x9b, },
 623                { .reg = 0x030d,                        .val = 0x00, },
 624                { .reg = 0x030e,                        .val = 0x1c, },
 625                { .reg = 0x0314,                        .val = 0xe1, },
 626                { .reg = 0x000d,                        .val = 0x82, },
 627                { .reg = LGDT3305_TP_CTRL_1,            .val = 0x5b, },
 628                { .reg = LGDT3305_TP_CTRL_1,            .val = 0x5b, },
 629        };
 630
 631        static struct lgdt3305_reg lgdt3305_init_data[] = {
 632                { .reg = LGDT3305_GEN_CTRL_1,           .val = 0x03, },
 633                { .reg = LGDT3305_GEN_CTRL_2,           .val = 0xb0, },
 634                { .reg = LGDT3305_GEN_CTRL_3,           .val = 0x01, },
 635                { .reg = LGDT3305_GEN_CONTROL,          .val = 0x6f, },
 636                { .reg = LGDT3305_GEN_CTRL_4,           .val = 0x03, },
 637                { .reg = LGDT3305_DGTL_AGC_REF_1,       .val = 0x32, },
 638                { .reg = LGDT3305_DGTL_AGC_REF_2,       .val = 0xc4, },
 639                { .reg = LGDT3305_CR_CTR_FREQ_1,        .val = 0x00, },
 640                { .reg = LGDT3305_CR_CTR_FREQ_2,        .val = 0x00, },
 641                { .reg = LGDT3305_CR_CTR_FREQ_3,        .val = 0x00, },
 642                { .reg = LGDT3305_CR_CTR_FREQ_4,        .val = 0x00, },
 643                { .reg = LGDT3305_CR_CTRL_7,            .val = 0x79, },
 644                { .reg = LGDT3305_AGC_POWER_REF_1,      .val = 0x32, },
 645                { .reg = LGDT3305_AGC_POWER_REF_2,      .val = 0xc4, },
 646                { .reg = LGDT3305_AGC_DELAY_PT_1,       .val = 0x0d, },
 647                { .reg = LGDT3305_AGC_DELAY_PT_2,       .val = 0x30, },
 648                { .reg = LGDT3305_RFAGC_LOOP_FLTR_BW_1, .val = 0x80, },
 649                { .reg = LGDT3305_RFAGC_LOOP_FLTR_BW_2, .val = 0x00, },
 650                { .reg = LGDT3305_IFBW_1,               .val = 0x80, },
 651                { .reg = LGDT3305_IFBW_2,               .val = 0x00, },
 652                { .reg = LGDT3305_AGC_CTRL_1,           .val = 0x30, },
 653                { .reg = LGDT3305_AGC_CTRL_4,           .val = 0x61, },
 654                { .reg = LGDT3305_FEC_BLOCK_CTRL,       .val = 0xff, },
 655                { .reg = LGDT3305_TP_CTRL_1,            .val = 0x1b, },
 656        };
 657
 658        lg_dbg("\n");
 659
 660        switch (state->cfg->demod_chip) {
 661        case LGDT3304:
 662                ret = lgdt3305_write_regs(state, lgdt3304_init_data,
 663                                          ARRAY_SIZE(lgdt3304_init_data));
 664                break;
 665        case LGDT3305:
 666                ret = lgdt3305_write_regs(state, lgdt3305_init_data,
 667                                          ARRAY_SIZE(lgdt3305_init_data));
 668                break;
 669        default:
 670                ret = -EINVAL;
 671        }
 672        if (lg_fail(ret))
 673                goto fail;
 674
 675        ret = lgdt3305_soft_reset(state);
 676fail:
 677        return ret;
 678}
 679
 680static int lgdt3304_set_parameters(struct dvb_frontend *fe,
 681                                   struct dvb_frontend_parameters *param)
 682{
 683        struct lgdt3305_state *state = fe->demodulator_priv;
 684        int ret;
 685
 686        lg_dbg("(%d, %d)\n", param->frequency, param->u.vsb.modulation);
 687
 688        if (fe->ops.tuner_ops.set_params) {
 689                ret = fe->ops.tuner_ops.set_params(fe, param);
 690                if (fe->ops.i2c_gate_ctrl)
 691                        fe->ops.i2c_gate_ctrl(fe, 0);
 692                if (lg_fail(ret))
 693                        goto fail;
 694                state->current_frequency = param->frequency;
 695        }
 696
 697        ret = lgdt3305_set_modulation(state, param);
 698        if (lg_fail(ret))
 699                goto fail;
 700
 701        ret = lgdt3305_passband_digital_agc(state, param);
 702        if (lg_fail(ret))
 703                goto fail;
 704
 705        ret = lgdt3305_agc_setup(state, param);
 706        if (lg_fail(ret))
 707                goto fail;
 708
 709        /* reg 0x030d is 3304-only... seen in vsb and qam usbsnoops... */
 710        switch (param->u.vsb.modulation) {
 711        case VSB_8:
 712                lgdt3305_write_reg(state, 0x030d, 0x00);
 713                lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_1, 0x4f);
 714                lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_2, 0x0c);
 715                lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_3, 0xac);
 716                lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_4, 0xba);
 717                break;
 718        case QAM_64:
 719        case QAM_256:
 720                lgdt3305_write_reg(state, 0x030d, 0x14);
 721                ret = lgdt3305_set_if(state, param);
 722                if (lg_fail(ret))
 723                        goto fail;
 724                break;
 725        default:
 726                return -EINVAL;
 727        }
 728
 729
 730        ret = lgdt3305_spectral_inversion(state, param,
 731                                          state->cfg->spectral_inversion
 732                                          ? 1 : 0);
 733        if (lg_fail(ret))
 734                goto fail;
 735
 736        state->current_modulation = param->u.vsb.modulation;
 737
 738        ret = lgdt3305_mpeg_mode(state, state->cfg->mpeg_mode);
 739        if (lg_fail(ret))
 740                goto fail;
 741
 742        /* lgdt3305_mpeg_mode_polarity calls lgdt3305_soft_reset */
 743        ret = lgdt3305_mpeg_mode_polarity(state,
 744                                          state->cfg->tpclk_edge,
 745                                          state->cfg->tpvalid_polarity);
 746fail:
 747        return ret;
 748}
 749
 750static int lgdt3305_set_parameters(struct dvb_frontend *fe,
 751                                   struct dvb_frontend_parameters *param)
 752{
 753        struct lgdt3305_state *state = fe->demodulator_priv;
 754        int ret;
 755
 756        lg_dbg("(%d, %d)\n", param->frequency, param->u.vsb.modulation);
 757
 758        if (fe->ops.tuner_ops.set_params) {
 759                ret = fe->ops.tuner_ops.set_params(fe, param);
 760                if (fe->ops.i2c_gate_ctrl)
 761                        fe->ops.i2c_gate_ctrl(fe, 0);
 762                if (lg_fail(ret))
 763                        goto fail;
 764                state->current_frequency = param->frequency;
 765        }
 766
 767        ret = lgdt3305_set_modulation(state, param);
 768        if (lg_fail(ret))
 769                goto fail;
 770
 771        ret = lgdt3305_passband_digital_agc(state, param);
 772        if (lg_fail(ret))
 773                goto fail;
 774        ret = lgdt3305_set_agc_power_ref(state, param);
 775        if (lg_fail(ret))
 776                goto fail;
 777        ret = lgdt3305_agc_setup(state, param);
 778        if (lg_fail(ret))
 779                goto fail;
 780
 781        /* low if */
 782        ret = lgdt3305_write_reg(state, LGDT3305_GEN_CONTROL, 0x2f);
 783        if (lg_fail(ret))
 784                goto fail;
 785        ret = lgdt3305_set_reg_bit(state, LGDT3305_CR_CTR_FREQ_1, 6, 1);
 786        if (lg_fail(ret))
 787                goto fail;
 788
 789        ret = lgdt3305_set_if(state, param);
 790        if (lg_fail(ret))
 791                goto fail;
 792        ret = lgdt3305_spectral_inversion(state, param,
 793                                          state->cfg->spectral_inversion
 794                                          ? 1 : 0);
 795        if (lg_fail(ret))
 796                goto fail;
 797
 798        ret = lgdt3305_set_filter_extension(state, param);
 799        if (lg_fail(ret))
 800                goto fail;
 801
 802        state->current_modulation = param->u.vsb.modulation;
 803
 804        ret = lgdt3305_mpeg_mode(state, state->cfg->mpeg_mode);
 805        if (lg_fail(ret))
 806                goto fail;
 807
 808        /* lgdt3305_mpeg_mode_polarity calls lgdt3305_soft_reset */
 809        ret = lgdt3305_mpeg_mode_polarity(state,
 810                                          state->cfg->tpclk_edge,
 811                                          state->cfg->tpvalid_polarity);
 812fail:
 813        return ret;
 814}
 815
 816static int lgdt3305_get_frontend(struct dvb_frontend *fe,
 817                                 struct dvb_frontend_parameters *param)
 818{
 819        struct lgdt3305_state *state = fe->demodulator_priv;
 820
 821        lg_dbg("\n");
 822
 823        param->u.vsb.modulation = state->current_modulation;
 824        param->frequency = state->current_frequency;
 825        return 0;
 826}
 827
 828/* ------------------------------------------------------------------------ */
 829
 830static int lgdt3305_read_cr_lock_status(struct lgdt3305_state *state,
 831                                        int *locked)
 832{
 833        u8 val;
 834        int ret;
 835        char *cr_lock_state = "";
 836
 837        *locked = 0;
 838
 839        ret = lgdt3305_read_reg(state, LGDT3305_CR_LOCK_STATUS, &val);
 840        if (lg_fail(ret))
 841                goto fail;
 842
 843        switch (state->current_modulation) {
 844        case QAM_256:
 845        case QAM_64:
 846                if (val & (1 << 1))
 847                        *locked = 1;
 848
 849                switch (val & 0x07) {
 850                case 0:
 851                        cr_lock_state = "QAM UNLOCK";
 852                        break;
 853                case 4:
 854                        cr_lock_state = "QAM 1stLock";
 855                        break;
 856                case 6:
 857                        cr_lock_state = "QAM 2ndLock";
 858                        break;
 859                case 7:
 860                        cr_lock_state = "QAM FinalLock";
 861                        break;
 862                default:
 863                        cr_lock_state = "CLOCKQAM-INVALID!";
 864                        break;
 865                }
 866                break;
 867        case VSB_8:
 868                if (val & (1 << 7)) {
 869                        *locked = 1;
 870                        cr_lock_state = "CLOCKVSB";
 871                }
 872                break;
 873        default:
 874                ret = -EINVAL;
 875        }
 876        lg_dbg("(%d) %s\n", *locked, cr_lock_state);
 877fail:
 878        return ret;
 879}
 880
 881static int lgdt3305_read_fec_lock_status(struct lgdt3305_state *state,
 882                                         int *locked)
 883{
 884        u8 val;
 885        int ret, mpeg_lock, fec_lock, viterbi_lock;
 886
 887        *locked = 0;
 888
 889        switch (state->current_modulation) {
 890        case QAM_256:
 891        case QAM_64:
 892                ret = lgdt3305_read_reg(state,
 893                                        LGDT3305_FEC_LOCK_STATUS, &val);
 894                if (lg_fail(ret))
 895                        goto fail;
 896
 897                mpeg_lock    = (val & (1 << 0)) ? 1 : 0;
 898                fec_lock     = (val & (1 << 2)) ? 1 : 0;
 899                viterbi_lock = (val & (1 << 3)) ? 1 : 0;
 900
 901                *locked = mpeg_lock && fec_lock && viterbi_lock;
 902
 903                lg_dbg("(%d) %s%s%s\n", *locked,
 904                       mpeg_lock    ? "mpeg lock  "  : "",
 905                       fec_lock     ? "fec lock  "   : "",
 906                       viterbi_lock ? "viterbi lock" : "");
 907                break;
 908        case VSB_8:
 909        default:
 910                ret = -EINVAL;
 911        }
 912fail:
 913        return ret;
 914}
 915
 916static int lgdt3305_read_status(struct dvb_frontend *fe, fe_status_t *status)
 917{
 918        struct lgdt3305_state *state = fe->demodulator_priv;
 919        u8 val;
 920        int ret, signal, inlock, nofecerr, snrgood,
 921                cr_lock, fec_lock, sync_lock;
 922
 923        *status = 0;
 924
 925        ret = lgdt3305_read_reg(state, LGDT3305_GEN_STATUS, &val);
 926        if (lg_fail(ret))
 927                goto fail;
 928
 929        signal    = (val & (1 << 4)) ? 1 : 0;
 930        inlock    = (val & (1 << 3)) ? 0 : 1;
 931        sync_lock = (val & (1 << 2)) ? 1 : 0;
 932        nofecerr  = (val & (1 << 1)) ? 1 : 0;
 933        snrgood   = (val & (1 << 0)) ? 1 : 0;
 934
 935        lg_dbg("%s%s%s%s%s\n",
 936               signal    ? "SIGNALEXIST " : "",
 937               inlock    ? "INLOCK "      : "",
 938               sync_lock ? "SYNCLOCK "    : "",
 939               nofecerr  ? "NOFECERR "    : "",
 940               snrgood   ? "SNRGOOD "     : "");
 941
 942        ret = lgdt3305_read_cr_lock_status(state, &cr_lock);
 943        if (lg_fail(ret))
 944                goto fail;
 945
 946        if (signal)
 947                *status |= FE_HAS_SIGNAL;
 948        if (cr_lock)
 949                *status |= FE_HAS_CARRIER;
 950        if (nofecerr)
 951                *status |= FE_HAS_VITERBI;
 952        if (sync_lock)
 953                *status |= FE_HAS_SYNC;
 954
 955        switch (state->current_modulation) {
 956        case QAM_256:
 957        case QAM_64:
 958                /* signal bit is unreliable on the DT3304 in QAM mode */
 959                if (((LGDT3304 == state->cfg->demod_chip)) && (cr_lock))
 960                        *status |= FE_HAS_SIGNAL;
 961
 962                ret = lgdt3305_read_fec_lock_status(state, &fec_lock);
 963                if (lg_fail(ret))
 964                        goto fail;
 965
 966                if (fec_lock)
 967                        *status |= FE_HAS_LOCK;
 968                break;
 969        case VSB_8:
 970                if (inlock)
 971                        *status |= FE_HAS_LOCK;
 972                break;
 973        default:
 974                ret = -EINVAL;
 975        }
 976fail:
 977        return ret;
 978}
 979
 980/* ------------------------------------------------------------------------ */
 981
 982/* borrowed from lgdt330x.c */
 983static u32 calculate_snr(u32 mse, u32 c)
 984{
 985        if (mse == 0) /* no signal */
 986                return 0;
 987
 988        mse = intlog10(mse);
 989        if (mse > c) {
 990                /* Negative SNR, which is possible, but realisticly the
 991                demod will lose lock before the signal gets this bad.  The
 992                API only allows for unsigned values, so just return 0 */
 993                return 0;
 994        }
 995        return 10*(c - mse);
 996}
 997
 998static int lgdt3305_read_snr(struct dvb_frontend *fe, u16 *snr)
 999{
1000        struct lgdt3305_state *state = fe->demodulator_priv;
1001        u32 noise;      /* noise value */
1002        u32 c;          /* per-modulation SNR calculation constant */
1003
1004        switch (state->current_modulation) {
1005        case VSB_8:
1006#ifdef USE_PTMSE
1007                /* Use Phase Tracker Mean-Square Error Register */
1008                /* SNR for ranges from -13.11 to +44.08 */
1009                noise = ((read_reg(state, LGDT3305_PT_MSE_1) & 0x07) << 16) |
1010                        (read_reg(state, LGDT3305_PT_MSE_2) << 8) |
1011                        (read_reg(state, LGDT3305_PT_MSE_3) & 0xff);
1012                c = 73957994; /* log10(25*32^2)*2^24 */
1013#else
1014                /* Use Equalizer Mean-Square Error Register */
1015                /* SNR for ranges from -16.12 to +44.08 */
1016                noise = ((read_reg(state, LGDT3305_EQ_MSE_1) & 0x0f) << 16) |
1017                        (read_reg(state, LGDT3305_EQ_MSE_2) << 8) |
1018                        (read_reg(state, LGDT3305_EQ_MSE_3) & 0xff);
1019                c = 73957994; /* log10(25*32^2)*2^24 */
1020#endif
1021                break;
1022        case QAM_64:
1023        case QAM_256:
1024                noise = (read_reg(state, LGDT3305_CR_MSE_1) << 8) |
1025                        (read_reg(state, LGDT3305_CR_MSE_2) & 0xff);
1026
1027                c = (state->current_modulation == QAM_64) ?
1028                        97939837 : 98026066;
1029                /* log10(688128)*2^24 and log10(696320)*2^24 */
1030                break;
1031        default:
1032                return -EINVAL;
1033        }
1034        state->snr = calculate_snr(noise, c);
1035        /* report SNR in dB * 10 */
1036        *snr = (state->snr / ((1 << 24) / 10));
1037        lg_dbg("noise = 0x%08x, snr = %d.%02d dB\n", noise,
1038               state->snr >> 24, (((state->snr >> 8) & 0xffff) * 100) >> 16);
1039
1040        return 0;
1041}
1042
1043static int lgdt3305_read_signal_strength(struct dvb_frontend *fe,
1044                                         u16 *strength)
1045{
1046        /* borrowed from lgdt330x.c
1047         *
1048         * Calculate strength from SNR up to 35dB
1049         * Even though the SNR can go higher than 35dB,
1050         * there is some comfort factor in having a range of
1051         * strong signals that can show at 100%
1052         */
1053        struct lgdt3305_state *state = fe->demodulator_priv;
1054        u16 snr;
1055        int ret;
1056
1057        *strength = 0;
1058
1059        ret = fe->ops.read_snr(fe, &snr);
1060        if (lg_fail(ret))
1061                goto fail;
1062        /* Rather than use the 8.8 value snr, use state->snr which is 8.24 */
1063        /* scale the range 0 - 35*2^24 into 0 - 65535 */
1064        if (state->snr >= 8960 * 0x10000)
1065                *strength = 0xffff;
1066        else
1067                *strength = state->snr / 8960;
1068fail:
1069        return ret;
1070}
1071
1072/* ------------------------------------------------------------------------ */
1073
1074static int lgdt3305_read_ber(struct dvb_frontend *fe, u32 *ber)
1075{
1076        *ber = 0;
1077        return 0;
1078}
1079
1080static int lgdt3305_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
1081{
1082        struct lgdt3305_state *state = fe->demodulator_priv;
1083
1084        *ucblocks =
1085                (read_reg(state, LGDT3305_FEC_PKT_ERR_1) << 8) |
1086                (read_reg(state, LGDT3305_FEC_PKT_ERR_2) & 0xff);
1087
1088        return 0;
1089}
1090
1091static int lgdt3305_get_tune_settings(struct dvb_frontend *fe,
1092                                      struct dvb_frontend_tune_settings
1093                                        *fe_tune_settings)
1094{
1095        fe_tune_settings->min_delay_ms = 500;
1096        lg_dbg("\n");
1097        return 0;
1098}
1099
1100static void lgdt3305_release(struct dvb_frontend *fe)
1101{
1102        struct lgdt3305_state *state = fe->demodulator_priv;
1103        lg_dbg("\n");
1104        kfree(state);
1105}
1106
1107static struct dvb_frontend_ops lgdt3304_ops;
1108static struct dvb_frontend_ops lgdt3305_ops;
1109
1110struct dvb_frontend *lgdt3305_attach(const struct lgdt3305_config *config,
1111                                     struct i2c_adapter *i2c_adap)
1112{
1113        struct lgdt3305_state *state = NULL;
1114        int ret;
1115        u8 val;
1116
1117        lg_dbg("(%d-%04x)\n",
1118               i2c_adap ? i2c_adapter_id(i2c_adap) : 0,
1119               config ? config->i2c_addr : 0);
1120
1121        state = kzalloc(sizeof(struct lgdt3305_state), GFP_KERNEL);
1122        if (state == NULL)
1123                goto fail;
1124
1125        state->cfg = config;
1126        state->i2c_adap = i2c_adap;
1127
1128        switch (config->demod_chip) {
1129        case LGDT3304:
1130                memcpy(&state->frontend.ops, &lgdt3304_ops,
1131                       sizeof(struct dvb_frontend_ops));
1132                break;
1133        case LGDT3305:
1134                memcpy(&state->frontend.ops, &lgdt3305_ops,
1135                       sizeof(struct dvb_frontend_ops));
1136                break;
1137        default:
1138                goto fail;
1139        }
1140        state->frontend.demodulator_priv = state;
1141
1142        /* verify that we're talking to a lg dt3304/5 */
1143        ret = lgdt3305_read_reg(state, LGDT3305_GEN_CTRL_2, &val);
1144        if ((lg_fail(ret)) | (val == 0))
1145                goto fail;
1146        ret = lgdt3305_write_reg(state, 0x0808, 0x80);
1147        if (lg_fail(ret))
1148                goto fail;
1149        ret = lgdt3305_read_reg(state, 0x0808, &val);
1150        if ((lg_fail(ret)) | (val != 0x80))
1151                goto fail;
1152        ret = lgdt3305_write_reg(state, 0x0808, 0x00);
1153        if (lg_fail(ret))
1154                goto fail;
1155
1156        state->current_frequency = -1;
1157        state->current_modulation = -1;
1158
1159        return &state->frontend;
1160fail:
1161        lg_warn("unable to detect %s hardware\n",
1162                config->demod_chip ? "LGDT3304" : "LGDT3305");
1163        kfree(state);
1164        return NULL;
1165}
1166EXPORT_SYMBOL(lgdt3305_attach);
1167
1168static struct dvb_frontend_ops lgdt3304_ops = {
1169        .info = {
1170                .name = "LG Electronics LGDT3304 VSB/QAM Frontend",
1171                .type               = FE_ATSC,
1172                .frequency_min      = 54000000,
1173                .frequency_max      = 858000000,
1174                .frequency_stepsize = 62500,
1175                .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB
1176        },
1177        .i2c_gate_ctrl        = lgdt3305_i2c_gate_ctrl,
1178        .init                 = lgdt3305_init,
1179        .set_frontend         = lgdt3304_set_parameters,
1180        .get_frontend         = lgdt3305_get_frontend,
1181        .get_tune_settings    = lgdt3305_get_tune_settings,
1182        .read_status          = lgdt3305_read_status,
1183        .read_ber             = lgdt3305_read_ber,
1184        .read_signal_strength = lgdt3305_read_signal_strength,
1185        .read_snr             = lgdt3305_read_snr,
1186        .read_ucblocks        = lgdt3305_read_ucblocks,
1187        .release              = lgdt3305_release,
1188};
1189
1190static struct dvb_frontend_ops lgdt3305_ops = {
1191        .info = {
1192                .name = "LG Electronics LGDT3305 VSB/QAM Frontend",
1193                .type               = FE_ATSC,
1194                .frequency_min      = 54000000,
1195                .frequency_max      = 858000000,
1196                .frequency_stepsize = 62500,
1197                .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB
1198        },
1199        .i2c_gate_ctrl        = lgdt3305_i2c_gate_ctrl,
1200        .init                 = lgdt3305_init,
1201        .sleep                = lgdt3305_sleep,
1202        .set_frontend         = lgdt3305_set_parameters,
1203        .get_frontend         = lgdt3305_get_frontend,
1204        .get_tune_settings    = lgdt3305_get_tune_settings,
1205        .read_status          = lgdt3305_read_status,
1206        .read_ber             = lgdt3305_read_ber,
1207        .read_signal_strength = lgdt3305_read_signal_strength,
1208        .read_snr             = lgdt3305_read_snr,
1209        .read_ucblocks        = lgdt3305_read_ucblocks,
1210        .release              = lgdt3305_release,
1211};
1212
1213MODULE_DESCRIPTION("LG Electronics LGDT3304/5 ATSC/QAM-B Demodulator Driver");
1214MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
1215MODULE_LICENSE("GPL");
1216MODULE_VERSION("0.2");
1217
1218/*
1219 * Local variables:
1220 * c-basic-offset: 8
1221 * End:
1222 */
1223