642 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 Conexant 22702 DVB OFDM demodulator driver4 5 based on:6 Alps TDMB7 DVB OFDM demodulator driver7 8 Copyright (C) 2001-2002 Convergence Integrated Media GmbH9 Holger Waechtler <holger@convergence.de>10 11 Copyright (C) 2004 Steven Toth <stoth@linuxtv.org>12 13 14*/15 16#include <linux/kernel.h>17#include <linux/init.h>18#include <linux/module.h>19#include <linux/string.h>20#include <linux/slab.h>21#include <linux/delay.h>22#include <media/dvb_frontend.h>23#include "cx22702.h"24 25struct cx22702_state {26 27 struct i2c_adapter *i2c;28 29 /* configuration settings */30 const struct cx22702_config *config;31 32 struct dvb_frontend frontend;33 34 /* previous uncorrected block counter */35 u8 prevUCBlocks;36};37 38static int debug;39module_param(debug, int, 0644);40MODULE_PARM_DESC(debug, "Enable verbose debug messages");41 42#define dprintk if (debug) printk43 44/* Register values to initialise the demod */45static const u8 init_tab[] = {46 0x00, 0x00, /* Stop acquisition */47 0x0B, 0x06,48 0x09, 0x01,49 0x0D, 0x41,50 0x16, 0x32,51 0x20, 0x0A,52 0x21, 0x17,53 0x24, 0x3e,54 0x26, 0xff,55 0x27, 0x10,56 0x28, 0x00,57 0x29, 0x00,58 0x2a, 0x10,59 0x2b, 0x00,60 0x2c, 0x10,61 0x2d, 0x00,62 0x48, 0xd4,63 0x49, 0x56,64 0x6b, 0x1e,65 0xc8, 0x02,66 0xf9, 0x00,67 0xfa, 0x00,68 0xfb, 0x00,69 0xfc, 0x00,70 0xfd, 0x00,71};72 73static int cx22702_writereg(struct cx22702_state *state, u8 reg, u8 data)74{75 int ret;76 u8 buf[] = { reg, data };77 struct i2c_msg msg = {78 .addr = state->config->demod_address, .flags = 0,79 .buf = buf, .len = 2 };80 81 ret = i2c_transfer(state->i2c, &msg, 1);82 83 if (unlikely(ret != 1)) {84 printk(KERN_ERR85 "%s: error (reg == 0x%02x, val == 0x%02x, ret == %i)\n",86 __func__, reg, data, ret);87 return -1;88 }89 90 return 0;91}92 93static u8 cx22702_readreg(struct cx22702_state *state, u8 reg)94{95 int ret;96 u8 data;97 98 struct i2c_msg msg[] = {99 { .addr = state->config->demod_address, .flags = 0,100 .buf = ®, .len = 1 },101 { .addr = state->config->demod_address, .flags = I2C_M_RD,102 .buf = &data, .len = 1 } };103 104 ret = i2c_transfer(state->i2c, msg, 2);105 106 if (unlikely(ret != 2)) {107 printk(KERN_ERR "%s: error (reg == 0x%02x, ret == %i)\n",108 __func__, reg, ret);109 return 0;110 }111 112 return data;113}114 115static int cx22702_set_inversion(struct cx22702_state *state, int inversion)116{117 u8 val;118 119 val = cx22702_readreg(state, 0x0C);120 switch (inversion) {121 case INVERSION_AUTO:122 return -EOPNOTSUPP;123 case INVERSION_ON:124 val |= 0x01;125 break;126 case INVERSION_OFF:127 val &= 0xfe;128 break;129 default:130 return -EINVAL;131 }132 return cx22702_writereg(state, 0x0C, val);133}134 135/* Retrieve the demod settings */136static int cx22702_get_tps(struct cx22702_state *state,137 struct dtv_frontend_properties *p)138{139 u8 val;140 141 /* Make sure the TPS regs are valid */142 if (!(cx22702_readreg(state, 0x0A) & 0x20))143 return -EAGAIN;144 145 val = cx22702_readreg(state, 0x01);146 switch ((val & 0x18) >> 3) {147 case 0:148 p->modulation = QPSK;149 break;150 case 1:151 p->modulation = QAM_16;152 break;153 case 2:154 p->modulation = QAM_64;155 break;156 }157 switch (val & 0x07) {158 case 0:159 p->hierarchy = HIERARCHY_NONE;160 break;161 case 1:162 p->hierarchy = HIERARCHY_1;163 break;164 case 2:165 p->hierarchy = HIERARCHY_2;166 break;167 case 3:168 p->hierarchy = HIERARCHY_4;169 break;170 }171 172 173 val = cx22702_readreg(state, 0x02);174 switch ((val & 0x38) >> 3) {175 case 0:176 p->code_rate_HP = FEC_1_2;177 break;178 case 1:179 p->code_rate_HP = FEC_2_3;180 break;181 case 2:182 p->code_rate_HP = FEC_3_4;183 break;184 case 3:185 p->code_rate_HP = FEC_5_6;186 break;187 case 4:188 p->code_rate_HP = FEC_7_8;189 break;190 }191 switch (val & 0x07) {192 case 0:193 p->code_rate_LP = FEC_1_2;194 break;195 case 1:196 p->code_rate_LP = FEC_2_3;197 break;198 case 2:199 p->code_rate_LP = FEC_3_4;200 break;201 case 3:202 p->code_rate_LP = FEC_5_6;203 break;204 case 4:205 p->code_rate_LP = FEC_7_8;206 break;207 }208 209 val = cx22702_readreg(state, 0x03);210 switch ((val & 0x0c) >> 2) {211 case 0:212 p->guard_interval = GUARD_INTERVAL_1_32;213 break;214 case 1:215 p->guard_interval = GUARD_INTERVAL_1_16;216 break;217 case 2:218 p->guard_interval = GUARD_INTERVAL_1_8;219 break;220 case 3:221 p->guard_interval = GUARD_INTERVAL_1_4;222 break;223 }224 switch (val & 0x03) {225 case 0:226 p->transmission_mode = TRANSMISSION_MODE_2K;227 break;228 case 1:229 p->transmission_mode = TRANSMISSION_MODE_8K;230 break;231 }232 233 return 0;234}235 236static int cx22702_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)237{238 struct cx22702_state *state = fe->demodulator_priv;239 u8 val;240 241 dprintk("%s(%d)\n", __func__, enable);242 val = cx22702_readreg(state, 0x0D);243 if (enable)244 val &= 0xfe;245 else246 val |= 0x01;247 return cx22702_writereg(state, 0x0D, val);248}249 250/* Talk to the demod, set the FEC, GUARD, QAM settings etc */251static int cx22702_set_tps(struct dvb_frontend *fe)252{253 struct dtv_frontend_properties *p = &fe->dtv_property_cache;254 u8 val;255 struct cx22702_state *state = fe->demodulator_priv;256 257 if (fe->ops.tuner_ops.set_params) {258 fe->ops.tuner_ops.set_params(fe);259 if (fe->ops.i2c_gate_ctrl)260 fe->ops.i2c_gate_ctrl(fe, 0);261 }262 263 /* set inversion */264 cx22702_set_inversion(state, p->inversion);265 266 /* set bandwidth */267 val = cx22702_readreg(state, 0x0C) & 0xcf;268 switch (p->bandwidth_hz) {269 case 6000000:270 val |= 0x20;271 break;272 case 7000000:273 val |= 0x10;274 break;275 case 8000000:276 break;277 default:278 dprintk("%s: invalid bandwidth\n", __func__);279 return -EINVAL;280 }281 cx22702_writereg(state, 0x0C, val);282 283 p->code_rate_LP = FEC_AUTO; /* temp hack as manual not working */284 285 /* use auto configuration? */286 if ((p->hierarchy == HIERARCHY_AUTO) ||287 (p->modulation == QAM_AUTO) ||288 (p->code_rate_HP == FEC_AUTO) ||289 (p->code_rate_LP == FEC_AUTO) ||290 (p->guard_interval == GUARD_INTERVAL_AUTO) ||291 (p->transmission_mode == TRANSMISSION_MODE_AUTO)) {292 293 /* TPS Source - use hardware driven values */294 cx22702_writereg(state, 0x06, 0x10);295 cx22702_writereg(state, 0x07, 0x9);296 cx22702_writereg(state, 0x08, 0xC1);297 cx22702_writereg(state, 0x0B, cx22702_readreg(state, 0x0B)298 & 0xfc);299 cx22702_writereg(state, 0x0C,300 (cx22702_readreg(state, 0x0C) & 0xBF) | 0x40);301 cx22702_writereg(state, 0x00, 0x01); /* Begin acquisition */302 dprintk("%s: Autodetecting\n", __func__);303 return 0;304 }305 306 /* manually programmed values */307 switch (p->modulation) { /* mask 0x18 */308 case QPSK:309 val = 0x00;310 break;311 case QAM_16:312 val = 0x08;313 break;314 case QAM_64:315 val = 0x10;316 break;317 default:318 dprintk("%s: invalid modulation\n", __func__);319 return -EINVAL;320 }321 switch (p->hierarchy) { /* mask 0x07 */322 case HIERARCHY_NONE:323 break;324 case HIERARCHY_1:325 val |= 0x01;326 break;327 case HIERARCHY_2:328 val |= 0x02;329 break;330 case HIERARCHY_4:331 val |= 0x03;332 break;333 default:334 dprintk("%s: invalid hierarchy\n", __func__);335 return -EINVAL;336 }337 cx22702_writereg(state, 0x06, val);338 339 switch (p->code_rate_HP) { /* mask 0x38 */340 case FEC_NONE:341 case FEC_1_2:342 val = 0x00;343 break;344 case FEC_2_3:345 val = 0x08;346 break;347 case FEC_3_4:348 val = 0x10;349 break;350 case FEC_5_6:351 val = 0x18;352 break;353 case FEC_7_8:354 val = 0x20;355 break;356 default:357 dprintk("%s: invalid code_rate_HP\n", __func__);358 return -EINVAL;359 }360 switch (p->code_rate_LP) { /* mask 0x07 */361 case FEC_NONE:362 case FEC_1_2:363 break;364 case FEC_2_3:365 val |= 0x01;366 break;367 case FEC_3_4:368 val |= 0x02;369 break;370 case FEC_5_6:371 val |= 0x03;372 break;373 case FEC_7_8:374 val |= 0x04;375 break;376 default:377 dprintk("%s: invalid code_rate_LP\n", __func__);378 return -EINVAL;379 }380 cx22702_writereg(state, 0x07, val);381 382 switch (p->guard_interval) { /* mask 0x0c */383 case GUARD_INTERVAL_1_32:384 val = 0x00;385 break;386 case GUARD_INTERVAL_1_16:387 val = 0x04;388 break;389 case GUARD_INTERVAL_1_8:390 val = 0x08;391 break;392 case GUARD_INTERVAL_1_4:393 val = 0x0c;394 break;395 default:396 dprintk("%s: invalid guard_interval\n", __func__);397 return -EINVAL;398 }399 switch (p->transmission_mode) { /* mask 0x03 */400 case TRANSMISSION_MODE_2K:401 break;402 case TRANSMISSION_MODE_8K:403 val |= 0x1;404 break;405 default:406 dprintk("%s: invalid transmission_mode\n", __func__);407 return -EINVAL;408 }409 cx22702_writereg(state, 0x08, val);410 cx22702_writereg(state, 0x0B,411 (cx22702_readreg(state, 0x0B) & 0xfc) | 0x02);412 cx22702_writereg(state, 0x0C,413 (cx22702_readreg(state, 0x0C) & 0xBF) | 0x40);414 415 /* Begin channel acquisition */416 cx22702_writereg(state, 0x00, 0x01);417 418 return 0;419}420 421/* Reset the demod hardware and reset all of the configuration registers422 to a default state. */423static int cx22702_init(struct dvb_frontend *fe)424{425 int i;426 struct cx22702_state *state = fe->demodulator_priv;427 428 cx22702_writereg(state, 0x00, 0x02);429 430 msleep(10);431 432 for (i = 0; i < ARRAY_SIZE(init_tab); i += 2)433 cx22702_writereg(state, init_tab[i], init_tab[i + 1]);434 435 cx22702_writereg(state, 0xf8, (state->config->output_mode << 1)436 & 0x02);437 438 cx22702_i2c_gate_ctrl(fe, 0);439 440 return 0;441}442 443static int cx22702_read_status(struct dvb_frontend *fe, enum fe_status *status)444{445 struct cx22702_state *state = fe->demodulator_priv;446 u8 reg0A;447 u8 reg23;448 449 *status = 0;450 451 reg0A = cx22702_readreg(state, 0x0A);452 reg23 = cx22702_readreg(state, 0x23);453 454 dprintk("%s: status demod=0x%02x agc=0x%02x\n"455 , __func__, reg0A, reg23);456 457 if (reg0A & 0x10) {458 *status |= FE_HAS_LOCK;459 *status |= FE_HAS_VITERBI;460 *status |= FE_HAS_SYNC;461 }462 463 if (reg0A & 0x20)464 *status |= FE_HAS_CARRIER;465 466 if (reg23 < 0xf0)467 *status |= FE_HAS_SIGNAL;468 469 return 0;470}471 472static int cx22702_read_ber(struct dvb_frontend *fe, u32 *ber)473{474 struct cx22702_state *state = fe->demodulator_priv;475 476 if (cx22702_readreg(state, 0xE4) & 0x02) {477 /* Realtime statistics */478 *ber = (cx22702_readreg(state, 0xDE) & 0x7F) << 7479 | (cx22702_readreg(state, 0xDF) & 0x7F);480 } else {481 /* Averagtine statistics */482 *ber = (cx22702_readreg(state, 0xDE) & 0x7F) << 7483 | cx22702_readreg(state, 0xDF);484 }485 486 return 0;487}488 489static int cx22702_read_signal_strength(struct dvb_frontend *fe,490 u16 *signal_strength)491{492 struct cx22702_state *state = fe->demodulator_priv;493 u8 reg23;494 495 /*496 * Experience suggests that the strength signal register works as497 * follows:498 * - In the absence of signal, value is 0xff.499 * - In the presence of a weak signal, bit 7 is set, not sure what500 * the lower 7 bits mean.501 * - In the presence of a strong signal, the register holds a 7-bit502 * value (bit 7 is cleared), with greater values standing for503 * weaker signals.504 */505 reg23 = cx22702_readreg(state, 0x23);506 if (reg23 & 0x80) {507 *signal_strength = 0;508 } else {509 reg23 = ~reg23 & 0x7f;510 /* Scale to 16 bit */511 *signal_strength = (reg23 << 9) | (reg23 << 2) | (reg23 >> 5);512 }513 514 return 0;515}516 517static int cx22702_read_snr(struct dvb_frontend *fe, u16 *snr)518{519 struct cx22702_state *state = fe->demodulator_priv;520 521 u16 rs_ber;522 if (cx22702_readreg(state, 0xE4) & 0x02) {523 /* Realtime statistics */524 rs_ber = (cx22702_readreg(state, 0xDE) & 0x7F) << 7525 | (cx22702_readreg(state, 0xDF) & 0x7F);526 } else {527 /* Averagine statistics */528 rs_ber = (cx22702_readreg(state, 0xDE) & 0x7F) << 8529 | cx22702_readreg(state, 0xDF);530 }531 *snr = ~rs_ber;532 533 return 0;534}535 536static int cx22702_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)537{538 struct cx22702_state *state = fe->demodulator_priv;539 540 u8 _ucblocks;541 542 /* RS Uncorrectable Packet Count then reset */543 _ucblocks = cx22702_readreg(state, 0xE3);544 if (state->prevUCBlocks < _ucblocks)545 *ucblocks = (_ucblocks - state->prevUCBlocks);546 else547 *ucblocks = state->prevUCBlocks - _ucblocks;548 state->prevUCBlocks = _ucblocks;549 550 return 0;551}552 553static int cx22702_get_frontend(struct dvb_frontend *fe,554 struct dtv_frontend_properties *c)555{556 struct cx22702_state *state = fe->demodulator_priv;557 558 u8 reg0C = cx22702_readreg(state, 0x0C);559 560 c->inversion = reg0C & 0x1 ? INVERSION_ON : INVERSION_OFF;561 return cx22702_get_tps(state, c);562}563 564static int cx22702_get_tune_settings(struct dvb_frontend *fe,565 struct dvb_frontend_tune_settings *tune)566{567 tune->min_delay_ms = 1000;568 return 0;569}570 571static void cx22702_release(struct dvb_frontend *fe)572{573 struct cx22702_state *state = fe->demodulator_priv;574 kfree(state);575}576 577static const struct dvb_frontend_ops cx22702_ops;578 579struct dvb_frontend *cx22702_attach(const struct cx22702_config *config,580 struct i2c_adapter *i2c)581{582 struct cx22702_state *state = NULL;583 584 /* allocate memory for the internal state */585 state = kzalloc(sizeof(struct cx22702_state), GFP_KERNEL);586 if (state == NULL)587 goto error;588 589 /* setup the state */590 state->config = config;591 state->i2c = i2c;592 593 /* check if the demod is there */594 if (cx22702_readreg(state, 0x1f) != 0x3)595 goto error;596 597 /* create dvb_frontend */598 memcpy(&state->frontend.ops, &cx22702_ops,599 sizeof(struct dvb_frontend_ops));600 state->frontend.demodulator_priv = state;601 return &state->frontend;602 603error:604 kfree(state);605 return NULL;606}607EXPORT_SYMBOL_GPL(cx22702_attach);608 609static const struct dvb_frontend_ops cx22702_ops = {610 .delsys = { SYS_DVBT },611 .info = {612 .name = "Conexant CX22702 DVB-T",613 .frequency_min_hz = 177 * MHz,614 .frequency_max_hz = 858 * MHz,615 .frequency_stepsize_hz = 166666,616 .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |617 FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |618 FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO |619 FE_CAN_HIERARCHY_AUTO | FE_CAN_GUARD_INTERVAL_AUTO |620 FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_RECOVER621 },622 623 .release = cx22702_release,624 625 .init = cx22702_init,626 .i2c_gate_ctrl = cx22702_i2c_gate_ctrl,627 628 .set_frontend = cx22702_set_tps,629 .get_frontend = cx22702_get_frontend,630 .get_tune_settings = cx22702_get_tune_settings,631 632 .read_status = cx22702_read_status,633 .read_ber = cx22702_read_ber,634 .read_signal_strength = cx22702_read_signal_strength,635 .read_snr = cx22702_read_snr,636 .read_ucblocks = cx22702_read_ucblocks,637};638 639MODULE_DESCRIPTION("Conexant CX22702 DVB-T Demodulator driver");640MODULE_AUTHOR("Steven Toth");641MODULE_LICENSE("GPL");642