Path: blob/master/drivers/media/dvb/frontends/au8522_dig.c
15112 views
/*1Auvitek AU8522 QAM/8VSB demodulator driver23Copyright (C) 2008 Steven Toth <[email protected]>45This program is free software; you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation; either version 2 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program; if not, write to the Free Software17Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.1819*/2021#include <linux/kernel.h>22#include <linux/init.h>23#include <linux/module.h>24#include <linux/string.h>25#include <linux/delay.h>26#include "dvb_frontend.h"27#include "au8522.h"28#include "au8522_priv.h"2930static int debug;3132/* Despite the name "hybrid_tuner", the framework works just as well for33hybrid demodulators as well... */34static LIST_HEAD(hybrid_tuner_instance_list);35static DEFINE_MUTEX(au8522_list_mutex);3637#define dprintk(arg...)\38do { if (debug)\39printk(arg);\40} while (0)4142/* 16 bit registers, 8 bit values */43int au8522_writereg(struct au8522_state *state, u16 reg, u8 data)44{45int ret;46u8 buf[] = { (reg >> 8) | 0x80, reg & 0xff, data };4748struct i2c_msg msg = { .addr = state->config->demod_address,49.flags = 0, .buf = buf, .len = 3 };5051ret = i2c_transfer(state->i2c, &msg, 1);5253if (ret != 1)54printk("%s: writereg error (reg == 0x%02x, val == 0x%04x, "55"ret == %i)\n", __func__, reg, data, ret);5657return (ret != 1) ? -1 : 0;58}5960u8 au8522_readreg(struct au8522_state *state, u16 reg)61{62int ret;63u8 b0[] = { (reg >> 8) | 0x40, reg & 0xff };64u8 b1[] = { 0 };6566struct i2c_msg msg[] = {67{ .addr = state->config->demod_address, .flags = 0,68.buf = b0, .len = 2 },69{ .addr = state->config->demod_address, .flags = I2C_M_RD,70.buf = b1, .len = 1 } };7172ret = i2c_transfer(state->i2c, msg, 2);7374if (ret != 2)75printk(KERN_ERR "%s: readreg error (ret == %i)\n",76__func__, ret);77return b1[0];78}7980static int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)81{82struct au8522_state *state = fe->demodulator_priv;8384dprintk("%s(%d)\n", __func__, enable);8586if (state->operational_mode == AU8522_ANALOG_MODE) {87/* We're being asked to manage the gate even though we're88not in digital mode. This can occur if we get switched89over to analog mode before the dvb_frontend kernel thread90has completely shutdown */91return 0;92}9394if (enable)95return au8522_writereg(state, 0x106, 1);96else97return au8522_writereg(state, 0x106, 0);98}99100struct mse2snr_tab {101u16 val;102u16 data;103};104105/* VSB SNR lookup table */106static struct mse2snr_tab vsb_mse2snr_tab[] = {107{ 0, 270 },108{ 2, 250 },109{ 3, 240 },110{ 5, 230 },111{ 7, 220 },112{ 9, 210 },113{ 12, 200 },114{ 13, 195 },115{ 15, 190 },116{ 17, 185 },117{ 19, 180 },118{ 21, 175 },119{ 24, 170 },120{ 27, 165 },121{ 31, 160 },122{ 32, 158 },123{ 33, 156 },124{ 36, 152 },125{ 37, 150 },126{ 39, 148 },127{ 40, 146 },128{ 41, 144 },129{ 43, 142 },130{ 44, 140 },131{ 48, 135 },132{ 50, 130 },133{ 43, 142 },134{ 53, 125 },135{ 56, 120 },136{ 256, 115 },137};138139/* QAM64 SNR lookup table */140static struct mse2snr_tab qam64_mse2snr_tab[] = {141{ 15, 0 },142{ 16, 290 },143{ 17, 288 },144{ 18, 286 },145{ 19, 284 },146{ 20, 282 },147{ 21, 281 },148{ 22, 279 },149{ 23, 277 },150{ 24, 275 },151{ 25, 273 },152{ 26, 271 },153{ 27, 269 },154{ 28, 268 },155{ 29, 266 },156{ 30, 264 },157{ 31, 262 },158{ 32, 260 },159{ 33, 259 },160{ 34, 258 },161{ 35, 256 },162{ 36, 255 },163{ 37, 254 },164{ 38, 252 },165{ 39, 251 },166{ 40, 250 },167{ 41, 249 },168{ 42, 248 },169{ 43, 246 },170{ 44, 245 },171{ 45, 244 },172{ 46, 242 },173{ 47, 241 },174{ 48, 240 },175{ 50, 239 },176{ 51, 238 },177{ 53, 237 },178{ 54, 236 },179{ 56, 235 },180{ 57, 234 },181{ 59, 233 },182{ 60, 232 },183{ 62, 231 },184{ 63, 230 },185{ 65, 229 },186{ 67, 228 },187{ 68, 227 },188{ 70, 226 },189{ 71, 225 },190{ 73, 224 },191{ 74, 223 },192{ 76, 222 },193{ 78, 221 },194{ 80, 220 },195{ 82, 219 },196{ 85, 218 },197{ 88, 217 },198{ 90, 216 },199{ 92, 215 },200{ 93, 214 },201{ 94, 212 },202{ 95, 211 },203{ 97, 210 },204{ 99, 209 },205{ 101, 208 },206{ 102, 207 },207{ 104, 206 },208{ 107, 205 },209{ 111, 204 },210{ 114, 203 },211{ 118, 202 },212{ 122, 201 },213{ 125, 200 },214{ 128, 199 },215{ 130, 198 },216{ 132, 197 },217{ 256, 190 },218};219220/* QAM256 SNR lookup table */221static struct mse2snr_tab qam256_mse2snr_tab[] = {222{ 16, 0 },223{ 17, 400 },224{ 18, 398 },225{ 19, 396 },226{ 20, 394 },227{ 21, 392 },228{ 22, 390 },229{ 23, 388 },230{ 24, 386 },231{ 25, 384 },232{ 26, 382 },233{ 27, 380 },234{ 28, 379 },235{ 29, 378 },236{ 30, 377 },237{ 31, 376 },238{ 32, 375 },239{ 33, 374 },240{ 34, 373 },241{ 35, 372 },242{ 36, 371 },243{ 37, 370 },244{ 38, 362 },245{ 39, 354 },246{ 40, 346 },247{ 41, 338 },248{ 42, 330 },249{ 43, 328 },250{ 44, 326 },251{ 45, 324 },252{ 46, 322 },253{ 47, 320 },254{ 48, 319 },255{ 49, 318 },256{ 50, 317 },257{ 51, 316 },258{ 52, 315 },259{ 53, 314 },260{ 54, 313 },261{ 55, 312 },262{ 56, 311 },263{ 57, 310 },264{ 58, 308 },265{ 59, 306 },266{ 60, 304 },267{ 61, 302 },268{ 62, 300 },269{ 63, 298 },270{ 65, 295 },271{ 68, 294 },272{ 70, 293 },273{ 73, 292 },274{ 76, 291 },275{ 78, 290 },276{ 79, 289 },277{ 81, 288 },278{ 82, 287 },279{ 83, 286 },280{ 84, 285 },281{ 85, 284 },282{ 86, 283 },283{ 88, 282 },284{ 89, 281 },285{ 256, 280 },286};287288static int au8522_mse2snr_lookup(struct mse2snr_tab *tab, int sz, int mse,289u16 *snr)290{291int i, ret = -EINVAL;292dprintk("%s()\n", __func__);293294for (i = 0; i < sz; i++) {295if (mse < tab[i].val) {296*snr = tab[i].data;297ret = 0;298break;299}300}301dprintk("%s() snr=%d\n", __func__, *snr);302return ret;303}304305static int au8522_set_if(struct dvb_frontend *fe, enum au8522_if_freq if_freq)306{307struct au8522_state *state = fe->demodulator_priv;308u8 r0b5, r0b6, r0b7;309char *ifmhz;310311switch (if_freq) {312case AU8522_IF_3_25MHZ:313ifmhz = "3.25";314r0b5 = 0x00;315r0b6 = 0x3d;316r0b7 = 0xa0;317break;318case AU8522_IF_4MHZ:319ifmhz = "4.00";320r0b5 = 0x00;321r0b6 = 0x4b;322r0b7 = 0xd9;323break;324case AU8522_IF_6MHZ:325ifmhz = "6.00";326r0b5 = 0xfb;327r0b6 = 0x8e;328r0b7 = 0x39;329break;330default:331dprintk("%s() IF Frequency not supported\n", __func__);332return -EINVAL;333}334dprintk("%s() %s MHz\n", __func__, ifmhz);335au8522_writereg(state, 0x80b5, r0b5);336au8522_writereg(state, 0x80b6, r0b6);337au8522_writereg(state, 0x80b7, r0b7);338339return 0;340}341342/* VSB Modulation table */343static struct {344u16 reg;345u16 data;346} VSB_mod_tab[] = {347{ 0x8090, 0x84 },348{ 0x4092, 0x11 },349{ 0x2005, 0x00 },350{ 0x8091, 0x80 },351{ 0x80a3, 0x0c },352{ 0x80a4, 0xe8 },353{ 0x8081, 0xc4 },354{ 0x80a5, 0x40 },355{ 0x80a7, 0x40 },356{ 0x80a6, 0x67 },357{ 0x8262, 0x20 },358{ 0x821c, 0x30 },359{ 0x80d8, 0x1a },360{ 0x8227, 0xa0 },361{ 0x8121, 0xff },362{ 0x80a8, 0xf0 },363{ 0x80a9, 0x05 },364{ 0x80aa, 0x77 },365{ 0x80ab, 0xf0 },366{ 0x80ac, 0x05 },367{ 0x80ad, 0x77 },368{ 0x80ae, 0x41 },369{ 0x80af, 0x66 },370{ 0x821b, 0xcc },371{ 0x821d, 0x80 },372{ 0x80a4, 0xe8 },373{ 0x8231, 0x13 },374};375376/* QAM64 Modulation table */377static struct {378u16 reg;379u16 data;380} QAM64_mod_tab[] = {381{ 0x00a3, 0x09 },382{ 0x00a4, 0x00 },383{ 0x0081, 0xc4 },384{ 0x00a5, 0x40 },385{ 0x00aa, 0x77 },386{ 0x00ad, 0x77 },387{ 0x00a6, 0x67 },388{ 0x0262, 0x20 },389{ 0x021c, 0x30 },390{ 0x00b8, 0x3e },391{ 0x00b9, 0xf0 },392{ 0x00ba, 0x01 },393{ 0x00bb, 0x18 },394{ 0x00bc, 0x50 },395{ 0x00bd, 0x00 },396{ 0x00be, 0xea },397{ 0x00bf, 0xef },398{ 0x00c0, 0xfc },399{ 0x00c1, 0xbd },400{ 0x00c2, 0x1f },401{ 0x00c3, 0xfc },402{ 0x00c4, 0xdd },403{ 0x00c5, 0xaf },404{ 0x00c6, 0x00 },405{ 0x00c7, 0x38 },406{ 0x00c8, 0x30 },407{ 0x00c9, 0x05 },408{ 0x00ca, 0x4a },409{ 0x00cb, 0xd0 },410{ 0x00cc, 0x01 },411{ 0x00cd, 0xd9 },412{ 0x00ce, 0x6f },413{ 0x00cf, 0xf9 },414{ 0x00d0, 0x70 },415{ 0x00d1, 0xdf },416{ 0x00d2, 0xf7 },417{ 0x00d3, 0xc2 },418{ 0x00d4, 0xdf },419{ 0x00d5, 0x02 },420{ 0x00d6, 0x9a },421{ 0x00d7, 0xd0 },422{ 0x0250, 0x0d },423{ 0x0251, 0xcd },424{ 0x0252, 0xe0 },425{ 0x0253, 0x05 },426{ 0x0254, 0xa7 },427{ 0x0255, 0xff },428{ 0x0256, 0xed },429{ 0x0257, 0x5b },430{ 0x0258, 0xae },431{ 0x0259, 0xe6 },432{ 0x025a, 0x3d },433{ 0x025b, 0x0f },434{ 0x025c, 0x0d },435{ 0x025d, 0xea },436{ 0x025e, 0xf2 },437{ 0x025f, 0x51 },438{ 0x0260, 0xf5 },439{ 0x0261, 0x06 },440{ 0x021a, 0x00 },441{ 0x0546, 0x40 },442{ 0x0210, 0xc7 },443{ 0x0211, 0xaa },444{ 0x0212, 0xab },445{ 0x0213, 0x02 },446{ 0x0502, 0x00 },447{ 0x0121, 0x04 },448{ 0x0122, 0x04 },449{ 0x052e, 0x10 },450{ 0x00a4, 0xca },451{ 0x00a7, 0x40 },452{ 0x0526, 0x01 },453};454455/* QAM256 Modulation table */456static struct {457u16 reg;458u16 data;459} QAM256_mod_tab[] = {460{ 0x80a3, 0x09 },461{ 0x80a4, 0x00 },462{ 0x8081, 0xc4 },463{ 0x80a5, 0x40 },464{ 0x80aa, 0x77 },465{ 0x80ad, 0x77 },466{ 0x80a6, 0x67 },467{ 0x8262, 0x20 },468{ 0x821c, 0x30 },469{ 0x80b8, 0x3e },470{ 0x80b9, 0xf0 },471{ 0x80ba, 0x01 },472{ 0x80bb, 0x18 },473{ 0x80bc, 0x50 },474{ 0x80bd, 0x00 },475{ 0x80be, 0xea },476{ 0x80bf, 0xef },477{ 0x80c0, 0xfc },478{ 0x80c1, 0xbd },479{ 0x80c2, 0x1f },480{ 0x80c3, 0xfc },481{ 0x80c4, 0xdd },482{ 0x80c5, 0xaf },483{ 0x80c6, 0x00 },484{ 0x80c7, 0x38 },485{ 0x80c8, 0x30 },486{ 0x80c9, 0x05 },487{ 0x80ca, 0x4a },488{ 0x80cb, 0xd0 },489{ 0x80cc, 0x01 },490{ 0x80cd, 0xd9 },491{ 0x80ce, 0x6f },492{ 0x80cf, 0xf9 },493{ 0x80d0, 0x70 },494{ 0x80d1, 0xdf },495{ 0x80d2, 0xf7 },496{ 0x80d3, 0xc2 },497{ 0x80d4, 0xdf },498{ 0x80d5, 0x02 },499{ 0x80d6, 0x9a },500{ 0x80d7, 0xd0 },501{ 0x8250, 0x0d },502{ 0x8251, 0xcd },503{ 0x8252, 0xe0 },504{ 0x8253, 0x05 },505{ 0x8254, 0xa7 },506{ 0x8255, 0xff },507{ 0x8256, 0xed },508{ 0x8257, 0x5b },509{ 0x8258, 0xae },510{ 0x8259, 0xe6 },511{ 0x825a, 0x3d },512{ 0x825b, 0x0f },513{ 0x825c, 0x0d },514{ 0x825d, 0xea },515{ 0x825e, 0xf2 },516{ 0x825f, 0x51 },517{ 0x8260, 0xf5 },518{ 0x8261, 0x06 },519{ 0x821a, 0x00 },520{ 0x8546, 0x40 },521{ 0x8210, 0x26 },522{ 0x8211, 0xf6 },523{ 0x8212, 0x84 },524{ 0x8213, 0x02 },525{ 0x8502, 0x01 },526{ 0x8121, 0x04 },527{ 0x8122, 0x04 },528{ 0x852e, 0x10 },529{ 0x80a4, 0xca },530{ 0x80a7, 0x40 },531{ 0x8526, 0x01 },532};533534static int au8522_enable_modulation(struct dvb_frontend *fe,535fe_modulation_t m)536{537struct au8522_state *state = fe->demodulator_priv;538int i;539540dprintk("%s(0x%08x)\n", __func__, m);541542switch (m) {543case VSB_8:544dprintk("%s() VSB_8\n", __func__);545for (i = 0; i < ARRAY_SIZE(VSB_mod_tab); i++)546au8522_writereg(state,547VSB_mod_tab[i].reg,548VSB_mod_tab[i].data);549au8522_set_if(fe, state->config->vsb_if);550break;551case QAM_64:552dprintk("%s() QAM 64\n", __func__);553for (i = 0; i < ARRAY_SIZE(QAM64_mod_tab); i++)554au8522_writereg(state,555QAM64_mod_tab[i].reg,556QAM64_mod_tab[i].data);557au8522_set_if(fe, state->config->qam_if);558break;559case QAM_256:560dprintk("%s() QAM 256\n", __func__);561for (i = 0; i < ARRAY_SIZE(QAM256_mod_tab); i++)562au8522_writereg(state,563QAM256_mod_tab[i].reg,564QAM256_mod_tab[i].data);565au8522_set_if(fe, state->config->qam_if);566break;567default:568dprintk("%s() Invalid modulation\n", __func__);569return -EINVAL;570}571572state->current_modulation = m;573574return 0;575}576577/* Talk to the demod, set the FEC, GUARD, QAM settings etc */578static int au8522_set_frontend(struct dvb_frontend *fe,579struct dvb_frontend_parameters *p)580{581struct au8522_state *state = fe->demodulator_priv;582int ret = -EINVAL;583584dprintk("%s(frequency=%d)\n", __func__, p->frequency);585586if ((state->current_frequency == p->frequency) &&587(state->current_modulation == p->u.vsb.modulation))588return 0;589590au8522_enable_modulation(fe, p->u.vsb.modulation);591592/* Allow the demod to settle */593msleep(100);594595if (fe->ops.tuner_ops.set_params) {596if (fe->ops.i2c_gate_ctrl)597fe->ops.i2c_gate_ctrl(fe, 1);598ret = fe->ops.tuner_ops.set_params(fe, p);599if (fe->ops.i2c_gate_ctrl)600fe->ops.i2c_gate_ctrl(fe, 0);601}602603if (ret < 0)604return ret;605606state->current_frequency = p->frequency;607608return 0;609}610611/* Reset the demod hardware and reset all of the configuration registers612to a default state. */613int au8522_init(struct dvb_frontend *fe)614{615struct au8522_state *state = fe->demodulator_priv;616dprintk("%s()\n", __func__);617618state->operational_mode = AU8522_DIGITAL_MODE;619620/* Clear out any state associated with the digital side of the621chip, so that when it gets powered back up it won't think622that it is already tuned */623state->current_frequency = 0;624625au8522_writereg(state, 0xa4, 1 << 5);626627au8522_i2c_gate_ctrl(fe, 1);628629return 0;630}631632static int au8522_led_gpio_enable(struct au8522_state *state, int onoff)633{634struct au8522_led_config *led_config = state->config->led_cfg;635u8 val;636637/* bail out if we can't control an LED */638if (!led_config || !led_config->gpio_output ||639!led_config->gpio_output_enable || !led_config->gpio_output_disable)640return 0;641642val = au8522_readreg(state, 0x4000 |643(led_config->gpio_output & ~0xc000));644if (onoff) {645/* enable GPIO output */646val &= ~((led_config->gpio_output_enable >> 8) & 0xff);647val |= (led_config->gpio_output_enable & 0xff);648} else {649/* disable GPIO output */650val &= ~((led_config->gpio_output_disable >> 8) & 0xff);651val |= (led_config->gpio_output_disable & 0xff);652}653return au8522_writereg(state, 0x8000 |654(led_config->gpio_output & ~0xc000), val);655}656657/* led = 0 | off658* led = 1 | signal ok659* led = 2 | signal strong660* led < 0 | only light led if leds are currently off661*/662static int au8522_led_ctrl(struct au8522_state *state, int led)663{664struct au8522_led_config *led_config = state->config->led_cfg;665int i, ret = 0;666667/* bail out if we can't control an LED */668if (!led_config || !led_config->gpio_leds ||669!led_config->num_led_states || !led_config->led_states)670return 0;671672if (led < 0) {673/* if LED is already lit, then leave it as-is */674if (state->led_state)675return 0;676else677led *= -1;678}679680/* toggle LED if changing state */681if (state->led_state != led) {682u8 val;683684dprintk("%s: %d\n", __func__, led);685686au8522_led_gpio_enable(state, 1);687688val = au8522_readreg(state, 0x4000 |689(led_config->gpio_leds & ~0xc000));690691/* start with all leds off */692for (i = 0; i < led_config->num_led_states; i++)693val &= ~led_config->led_states[i];694695/* set selected LED state */696if (led < led_config->num_led_states)697val |= led_config->led_states[led];698else if (led_config->num_led_states)699val |=700led_config->led_states[led_config->num_led_states - 1];701702ret = au8522_writereg(state, 0x8000 |703(led_config->gpio_leds & ~0xc000), val);704if (ret < 0)705return ret;706707state->led_state = led;708709if (led == 0)710au8522_led_gpio_enable(state, 0);711}712713return 0;714}715716int au8522_sleep(struct dvb_frontend *fe)717{718struct au8522_state *state = fe->demodulator_priv;719dprintk("%s()\n", __func__);720721/* Only power down if the digital side is currently using the chip */722if (state->operational_mode == AU8522_ANALOG_MODE) {723/* We're not in one of the expected power modes, which means724that the DVB thread is probably telling us to go to sleep725even though the analog frontend has already started using726the chip. So ignore the request */727return 0;728}729730/* turn off led */731au8522_led_ctrl(state, 0);732733/* Power down the chip */734au8522_writereg(state, 0xa4, 1 << 5);735736state->current_frequency = 0;737738return 0;739}740741static int au8522_read_status(struct dvb_frontend *fe, fe_status_t *status)742{743struct au8522_state *state = fe->demodulator_priv;744u8 reg;745u32 tuner_status = 0;746747*status = 0;748749if (state->current_modulation == VSB_8) {750dprintk("%s() Checking VSB_8\n", __func__);751reg = au8522_readreg(state, 0x4088);752if ((reg & 0x03) == 0x03)753*status |= FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI;754} else {755dprintk("%s() Checking QAM\n", __func__);756reg = au8522_readreg(state, 0x4541);757if (reg & 0x80)758*status |= FE_HAS_VITERBI;759if (reg & 0x20)760*status |= FE_HAS_LOCK | FE_HAS_SYNC;761}762763switch (state->config->status_mode) {764case AU8522_DEMODLOCKING:765dprintk("%s() DEMODLOCKING\n", __func__);766if (*status & FE_HAS_VITERBI)767*status |= FE_HAS_CARRIER | FE_HAS_SIGNAL;768break;769case AU8522_TUNERLOCKING:770/* Get the tuner status */771dprintk("%s() TUNERLOCKING\n", __func__);772if (fe->ops.tuner_ops.get_status) {773if (fe->ops.i2c_gate_ctrl)774fe->ops.i2c_gate_ctrl(fe, 1);775776fe->ops.tuner_ops.get_status(fe, &tuner_status);777778if (fe->ops.i2c_gate_ctrl)779fe->ops.i2c_gate_ctrl(fe, 0);780}781if (tuner_status)782*status |= FE_HAS_CARRIER | FE_HAS_SIGNAL;783break;784}785state->fe_status = *status;786787if (*status & FE_HAS_LOCK)788/* turn on LED, if it isn't on already */789au8522_led_ctrl(state, -1);790else791/* turn off LED */792au8522_led_ctrl(state, 0);793794dprintk("%s() status 0x%08x\n", __func__, *status);795796return 0;797}798799static int au8522_led_status(struct au8522_state *state, const u16 *snr)800{801struct au8522_led_config *led_config = state->config->led_cfg;802int led;803u16 strong;804805/* bail out if we can't control an LED */806if (!led_config)807return 0;808809if (0 == (state->fe_status & FE_HAS_LOCK))810return au8522_led_ctrl(state, 0);811else if (state->current_modulation == QAM_256)812strong = led_config->qam256_strong;813else if (state->current_modulation == QAM_64)814strong = led_config->qam64_strong;815else /* (state->current_modulation == VSB_8) */816strong = led_config->vsb8_strong;817818if (*snr >= strong)819led = 2;820else821led = 1;822823if ((state->led_state) &&824(((strong < *snr) ? (*snr - strong) : (strong - *snr)) <= 10))825/* snr didn't change enough to bother826* changing the color of the led */827return 0;828829return au8522_led_ctrl(state, led);830}831832static int au8522_read_snr(struct dvb_frontend *fe, u16 *snr)833{834struct au8522_state *state = fe->demodulator_priv;835int ret = -EINVAL;836837dprintk("%s()\n", __func__);838839if (state->current_modulation == QAM_256)840ret = au8522_mse2snr_lookup(qam256_mse2snr_tab,841ARRAY_SIZE(qam256_mse2snr_tab),842au8522_readreg(state, 0x4522),843snr);844else if (state->current_modulation == QAM_64)845ret = au8522_mse2snr_lookup(qam64_mse2snr_tab,846ARRAY_SIZE(qam64_mse2snr_tab),847au8522_readreg(state, 0x4522),848snr);849else /* VSB_8 */850ret = au8522_mse2snr_lookup(vsb_mse2snr_tab,851ARRAY_SIZE(vsb_mse2snr_tab),852au8522_readreg(state, 0x4311),853snr);854855if (state->config->led_cfg)856au8522_led_status(state, snr);857858return ret;859}860861static int au8522_read_signal_strength(struct dvb_frontend *fe,862u16 *signal_strength)863{864return au8522_read_snr(fe, signal_strength);865}866867static int au8522_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)868{869struct au8522_state *state = fe->demodulator_priv;870871if (state->current_modulation == VSB_8)872*ucblocks = au8522_readreg(state, 0x4087);873else874*ucblocks = au8522_readreg(state, 0x4543);875876return 0;877}878879static int au8522_read_ber(struct dvb_frontend *fe, u32 *ber)880{881return au8522_read_ucblocks(fe, ber);882}883884static int au8522_get_frontend(struct dvb_frontend *fe,885struct dvb_frontend_parameters *p)886{887struct au8522_state *state = fe->demodulator_priv;888889p->frequency = state->current_frequency;890p->u.vsb.modulation = state->current_modulation;891892return 0;893}894895static int au8522_get_tune_settings(struct dvb_frontend *fe,896struct dvb_frontend_tune_settings *tune)897{898tune->min_delay_ms = 1000;899return 0;900}901902static struct dvb_frontend_ops au8522_ops;903904int au8522_get_state(struct au8522_state **state, struct i2c_adapter *i2c,905u8 client_address)906{907int ret;908909mutex_lock(&au8522_list_mutex);910ret = hybrid_tuner_request_state(struct au8522_state, (*state),911hybrid_tuner_instance_list,912i2c, client_address, "au8522");913mutex_unlock(&au8522_list_mutex);914915return ret;916}917918void au8522_release_state(struct au8522_state *state)919{920mutex_lock(&au8522_list_mutex);921if (state != NULL)922hybrid_tuner_release_state(state);923mutex_unlock(&au8522_list_mutex);924}925926927static void au8522_release(struct dvb_frontend *fe)928{929struct au8522_state *state = fe->demodulator_priv;930au8522_release_state(state);931}932933struct dvb_frontend *au8522_attach(const struct au8522_config *config,934struct i2c_adapter *i2c)935{936struct au8522_state *state = NULL;937int instance;938939/* allocate memory for the internal state */940instance = au8522_get_state(&state, i2c, config->demod_address);941switch (instance) {942case 0:943dprintk("%s state allocation failed\n", __func__);944break;945case 1:946/* new demod instance */947dprintk("%s using new instance\n", __func__);948break;949default:950/* existing demod instance */951dprintk("%s using existing instance\n", __func__);952break;953}954955/* setup the state */956state->config = config;957state->i2c = i2c;958state->operational_mode = AU8522_DIGITAL_MODE;959960/* create dvb_frontend */961memcpy(&state->frontend.ops, &au8522_ops,962sizeof(struct dvb_frontend_ops));963state->frontend.demodulator_priv = state;964965if (au8522_init(&state->frontend) != 0) {966printk(KERN_ERR "%s: Failed to initialize correctly\n",967__func__);968goto error;969}970971/* Note: Leaving the I2C gate open here. */972au8522_i2c_gate_ctrl(&state->frontend, 1);973974return &state->frontend;975976error:977au8522_release_state(state);978return NULL;979}980EXPORT_SYMBOL(au8522_attach);981982static struct dvb_frontend_ops au8522_ops = {983984.info = {985.name = "Auvitek AU8522 QAM/8VSB Frontend",986.type = FE_ATSC,987.frequency_min = 54000000,988.frequency_max = 858000000,989.frequency_stepsize = 62500,990.caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB991},992993.init = au8522_init,994.sleep = au8522_sleep,995.i2c_gate_ctrl = au8522_i2c_gate_ctrl,996.set_frontend = au8522_set_frontend,997.get_frontend = au8522_get_frontend,998.get_tune_settings = au8522_get_tune_settings,999.read_status = au8522_read_status,1000.read_ber = au8522_read_ber,1001.read_signal_strength = au8522_read_signal_strength,1002.read_snr = au8522_read_snr,1003.read_ucblocks = au8522_read_ucblocks,1004.release = au8522_release,1005};10061007module_param(debug, int, 0644);1008MODULE_PARM_DESC(debug, "Enable verbose debug messages");10091010MODULE_DESCRIPTION("Auvitek AU8522 QAM-B/ATSC Demodulator driver");1011MODULE_AUTHOR("Steven Toth");1012MODULE_LICENSE("GPL");101310141015