diff options
Diffstat (limited to 'drivers/staging/media/as102/as102_fe.c')
-rw-r--r-- | drivers/staging/media/as102/as102_fe.c | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/drivers/staging/media/as102/as102_fe.c b/drivers/staging/media/as102/as102_fe.c new file mode 100644 index 0000000..3550f90 --- /dev/null +++ b/drivers/staging/media/as102/as102_fe.c @@ -0,0 +1,603 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/version.h> + +#include "as102_drv.h" +#include "as10x_types.h" +#include "as10x_cmd.h" + +extern int elna_enable; + +static void as10x_fe_copy_tps_parameters(struct dvb_frontend_parameters *dst, + struct as10x_tps *src); + +static void as102_fe_copy_tune_parameters(struct as10x_tune_args *dst, + struct dvb_frontend_parameters *src); + +static int as102_fe_set_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *params) +{ + int ret = 0; + struct as102_dev_t *dev; + struct as10x_tune_args tune_args = { 0 }; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + as102_fe_copy_tune_parameters(&tune_args, params); + + /* send abilis command: SET_TUNE */ + ret = as10x_cmd_set_tune(&dev->bus_adap, &tune_args); + if (ret != 0) + dprintk(debug, "as10x_cmd_set_tune failed. (err = %d)\n", ret); + + mutex_unlock(&dev->bus_adap.lock); + + LEAVE(); + return (ret < 0) ? -EINVAL : 0; +} + +static int as102_fe_get_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p) { + int ret = 0; + struct as102_dev_t *dev; + struct as10x_tps tps = { 0 }; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -EINVAL; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + /* send abilis command: GET_TPS */ + ret = as10x_cmd_get_tps(&dev->bus_adap, &tps); + + if (ret == 0) + as10x_fe_copy_tps_parameters(p, &tps); + + mutex_unlock(&dev->bus_adap.lock); + + LEAVE(); + return (ret < 0) ? -EINVAL : 0; +} + +static int as102_fe_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *settings) { + ENTER(); + +#if 0 + dprintk(debug, "step_size = %d\n", settings->step_size); + dprintk(debug, "max_drift = %d\n", settings->max_drift); + dprintk(debug, "min_delay_ms = %d -> %d\n", settings->min_delay_ms, + 1000); +#endif + + settings->min_delay_ms = 1000; + + LEAVE(); + return 0; +} + + +static int as102_fe_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + int ret = 0; + struct as102_dev_t *dev; + struct as10x_tune_status tstate = { 0 }; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + /* send abilis command: GET_TUNE_STATUS */ + ret = as10x_cmd_get_tune_status(&dev->bus_adap, &tstate); + if (ret < 0) { + dprintk(debug, "as10x_cmd_get_tune_status failed (err = %d)\n", + ret); + goto out; + } + + dev->signal_strength = tstate.signal_strength; + dev->ber = tstate.BER; + + switch (tstate.tune_state) { + case TUNE_STATUS_SIGNAL_DVB_OK: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; + break; + case TUNE_STATUS_STREAM_DETECTED: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC; + break; + case TUNE_STATUS_STREAM_TUNED: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC | + FE_HAS_LOCK; + break; + default: + *status = TUNE_STATUS_NOT_TUNED; + } + + dprintk(debug, "tuner status: 0x%02x, strength %d, per: %d, ber: %d\n", + tstate.tune_state, tstate.signal_strength, + tstate.PER, tstate.BER); + + if (*status & FE_HAS_LOCK) { + if (as10x_cmd_get_demod_stats(&dev->bus_adap, + (struct as10x_demod_stats *) &dev->demod_stats) < 0) { + memset(&dev->demod_stats, 0, sizeof(dev->demod_stats)); + dprintk(debug, "as10x_cmd_get_demod_stats failed " + "(probably not tuned)\n"); + } else { + dprintk(debug, + "demod status: fc: 0x%08x, bad fc: 0x%08x, " + "bytes corrected: 0x%08x , MER: 0x%04x\n", + dev->demod_stats.frame_count, + dev->demod_stats.bad_frame_count, + dev->demod_stats.bytes_fixed_by_rs, + dev->demod_stats.mer); + } + } else { + memset(&dev->demod_stats, 0, sizeof(dev->demod_stats)); + } + +out: + mutex_unlock(&dev->bus_adap.lock); + LEAVE(); + return ret; +} + +/* + * Note: + * - in AS102 SNR=MER + * - the SNR will be returned in linear terms, i.e. not in dB + * - the accuracy equals ±2dB for a SNR range from 4dB to 30dB + * - the accuracy is >2dB for SNR values outside this range + */ +static int as102_fe_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct as102_dev_t *dev; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + *snr = dev->demod_stats.mer; + + LEAVE(); + return 0; +} + +static int as102_fe_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct as102_dev_t *dev; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + *ber = dev->ber; + + LEAVE(); + return 0; +} + +static int as102_fe_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + struct as102_dev_t *dev; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + *strength = (((0xffff * 400) * dev->signal_strength + 41000) * 2); + + LEAVE(); + return 0; +} + +static int as102_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct as102_dev_t *dev; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (dev->demod_stats.has_started) + *ucblocks = dev->demod_stats.bad_frame_count; + else + *ucblocks = 0; + + LEAVE(); + return 0; +} + +static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) +{ + struct as102_dev_t *dev; + int ret; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + if (acquire) { + if (elna_enable) + as10x_cmd_set_context(&dev->bus_adap, 1010, 0xC0); + + ret = as10x_cmd_turn_on(&dev->bus_adap); + } else { + ret = as10x_cmd_turn_off(&dev->bus_adap); + } + + mutex_unlock(&dev->bus_adap.lock); + + LEAVE(); + return ret; +} + +static struct dvb_frontend_ops as102_fe_ops = { + .info = { + .name = "Unknown AS102 device", + .type = FE_OFDM, + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 166667, + .caps = FE_CAN_INVERSION_AUTO + | FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 + | FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO + | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QPSK + | FE_CAN_QAM_AUTO + | FE_CAN_TRANSMISSION_MODE_AUTO + | FE_CAN_GUARD_INTERVAL_AUTO + | FE_CAN_HIERARCHY_AUTO + | FE_CAN_RECOVER + | FE_CAN_MUTE_TS + }, + + .set_frontend = as102_fe_set_frontend, + .get_frontend = as102_fe_get_frontend, + .get_tune_settings = as102_fe_get_tune_settings, + + .read_status = as102_fe_read_status, + .read_snr = as102_fe_read_snr, + .read_ber = as102_fe_read_ber, + .read_signal_strength = as102_fe_read_signal_strength, + .read_ucblocks = as102_fe_read_ucblocks, + .ts_bus_ctrl = as102_fe_ts_bus_ctrl, +}; + +int as102_dvb_unregister_fe(struct dvb_frontend *fe) +{ + /* unregister frontend */ + dvb_unregister_frontend(fe); + + /* detach frontend */ + dvb_frontend_detach(fe); + + return 0; +} + +int as102_dvb_register_fe(struct as102_dev_t *as102_dev, + struct dvb_frontend *dvb_fe) +{ + int errno; + struct dvb_adapter *dvb_adap; + + if (as102_dev == NULL) + return -EINVAL; + + /* extract dvb_adapter */ + dvb_adap = &as102_dev->dvb_adap; + + /* init frontend callback ops */ + memcpy(&dvb_fe->ops, &as102_fe_ops, sizeof(struct dvb_frontend_ops)); + strncpy(dvb_fe->ops.info.name, as102_dev->name, + sizeof(dvb_fe->ops.info.name)); + + /* register dbvb frontend */ + errno = dvb_register_frontend(dvb_adap, dvb_fe); + if (errno == 0) + dvb_fe->tuner_priv = as102_dev; + + return errno; +} + +static void as10x_fe_copy_tps_parameters(struct dvb_frontend_parameters *dst, + struct as10x_tps *as10x_tps) +{ + + struct dvb_ofdm_parameters *fe_tps = &dst->u.ofdm; + + /* extract consteallation */ + switch (as10x_tps->constellation) { + case CONST_QPSK: + fe_tps->constellation = QPSK; + break; + case CONST_QAM16: + fe_tps->constellation = QAM_16; + break; + case CONST_QAM64: + fe_tps->constellation = QAM_64; + break; + } + + /* extract hierarchy */ + switch (as10x_tps->hierarchy) { + case HIER_NONE: + fe_tps->hierarchy_information = HIERARCHY_NONE; + break; + case HIER_ALPHA_1: + fe_tps->hierarchy_information = HIERARCHY_1; + break; + case HIER_ALPHA_2: + fe_tps->hierarchy_information = HIERARCHY_2; + break; + case HIER_ALPHA_4: + fe_tps->hierarchy_information = HIERARCHY_4; + break; + } + + /* extract code rate HP */ + switch (as10x_tps->code_rate_HP) { + case CODE_RATE_1_2: + fe_tps->code_rate_HP = FEC_1_2; + break; + case CODE_RATE_2_3: + fe_tps->code_rate_HP = FEC_2_3; + break; + case CODE_RATE_3_4: + fe_tps->code_rate_HP = FEC_3_4; + break; + case CODE_RATE_5_6: + fe_tps->code_rate_HP = FEC_5_6; + break; + case CODE_RATE_7_8: + fe_tps->code_rate_HP = FEC_7_8; + break; + } + + /* extract code rate LP */ + switch (as10x_tps->code_rate_LP) { + case CODE_RATE_1_2: + fe_tps->code_rate_LP = FEC_1_2; + break; + case CODE_RATE_2_3: + fe_tps->code_rate_LP = FEC_2_3; + break; + case CODE_RATE_3_4: + fe_tps->code_rate_LP = FEC_3_4; + break; + case CODE_RATE_5_6: + fe_tps->code_rate_LP = FEC_5_6; + break; + case CODE_RATE_7_8: + fe_tps->code_rate_LP = FEC_7_8; + break; + } + + /* extract guard interval */ + switch (as10x_tps->guard_interval) { + case GUARD_INT_1_32: + fe_tps->guard_interval = GUARD_INTERVAL_1_32; + break; + case GUARD_INT_1_16: + fe_tps->guard_interval = GUARD_INTERVAL_1_16; + break; + case GUARD_INT_1_8: + fe_tps->guard_interval = GUARD_INTERVAL_1_8; + break; + case GUARD_INT_1_4: + fe_tps->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + /* extract transmission mode */ + switch (as10x_tps->transmission_mode) { + case TRANS_MODE_2K: + fe_tps->transmission_mode = TRANSMISSION_MODE_2K; + break; + case TRANS_MODE_8K: + fe_tps->transmission_mode = TRANSMISSION_MODE_8K; + break; + } +} + +static uint8_t as102_fe_get_code_rate(fe_code_rate_t arg) +{ + uint8_t c; + + switch (arg) { + case FEC_1_2: + c = CODE_RATE_1_2; + break; + case FEC_2_3: + c = CODE_RATE_2_3; + break; + case FEC_3_4: + c = CODE_RATE_3_4; + break; + case FEC_5_6: + c = CODE_RATE_5_6; + break; + case FEC_7_8: + c = CODE_RATE_7_8; + break; + default: + c = CODE_RATE_UNKNOWN; + break; + } + + return c; +} + +static void as102_fe_copy_tune_parameters(struct as10x_tune_args *tune_args, + struct dvb_frontend_parameters *params) +{ + + /* set frequency */ + tune_args->freq = params->frequency / 1000; + + /* fix interleaving_mode */ + tune_args->interleaving_mode = INTLV_NATIVE; + + switch (params->u.ofdm.bandwidth) { + case BANDWIDTH_8_MHZ: + tune_args->bandwidth = BW_8_MHZ; + break; + case BANDWIDTH_7_MHZ: + tune_args->bandwidth = BW_7_MHZ; + break; + case BANDWIDTH_6_MHZ: + tune_args->bandwidth = BW_6_MHZ; + break; + default: + tune_args->bandwidth = BW_8_MHZ; + } + + switch (params->u.ofdm.guard_interval) { + case GUARD_INTERVAL_1_32: + tune_args->guard_interval = GUARD_INT_1_32; + break; + case GUARD_INTERVAL_1_16: + tune_args->guard_interval = GUARD_INT_1_16; + break; + case GUARD_INTERVAL_1_8: + tune_args->guard_interval = GUARD_INT_1_8; + break; + case GUARD_INTERVAL_1_4: + tune_args->guard_interval = GUARD_INT_1_4; + break; + case GUARD_INTERVAL_AUTO: + default: + tune_args->guard_interval = GUARD_UNKNOWN; + break; + } + + switch (params->u.ofdm.constellation) { + case QPSK: + tune_args->constellation = CONST_QPSK; + break; + case QAM_16: + tune_args->constellation = CONST_QAM16; + break; + case QAM_64: + tune_args->constellation = CONST_QAM64; + break; + default: + tune_args->constellation = CONST_UNKNOWN; + break; + } + + switch (params->u.ofdm.transmission_mode) { + case TRANSMISSION_MODE_2K: + tune_args->transmission_mode = TRANS_MODE_2K; + break; + case TRANSMISSION_MODE_8K: + tune_args->transmission_mode = TRANS_MODE_8K; + break; + default: + tune_args->transmission_mode = TRANS_MODE_UNKNOWN; + } + + switch (params->u.ofdm.hierarchy_information) { + case HIERARCHY_NONE: + tune_args->hierarchy = HIER_NONE; + break; + case HIERARCHY_1: + tune_args->hierarchy = HIER_ALPHA_1; + break; + case HIERARCHY_2: + tune_args->hierarchy = HIER_ALPHA_2; + break; + case HIERARCHY_4: + tune_args->hierarchy = HIER_ALPHA_4; + break; + case HIERARCHY_AUTO: + tune_args->hierarchy = HIER_UNKNOWN; + break; + } + + dprintk(debug, "tuner parameters: freq: %d bw: 0x%02x gi: 0x%02x\n", + params->frequency, + tune_args->bandwidth, + tune_args->guard_interval); + + /* + * Detect a hierarchy selection + * if HP/LP are both set to FEC_NONE, HP will be selected. + */ + if ((tune_args->hierarchy != HIER_NONE) && + ((params->u.ofdm.code_rate_LP == FEC_NONE) || + (params->u.ofdm.code_rate_HP == FEC_NONE))) { + + if (params->u.ofdm.code_rate_LP == FEC_NONE) { + tune_args->hier_select = HIER_HIGH_PRIORITY; + tune_args->code_rate = + as102_fe_get_code_rate(params->u.ofdm.code_rate_HP); + } + + if (params->u.ofdm.code_rate_HP == FEC_NONE) { + tune_args->hier_select = HIER_LOW_PRIORITY; + tune_args->code_rate = + as102_fe_get_code_rate(params->u.ofdm.code_rate_LP); + } + + dprintk(debug, "\thierarchy: 0x%02x " + "selected: %s code_rate_%s: 0x%02x\n", + tune_args->hierarchy, + tune_args->hier_select == HIER_HIGH_PRIORITY ? + "HP" : "LP", + tune_args->hier_select == HIER_HIGH_PRIORITY ? + "HP" : "LP", + tune_args->code_rate); + } else { + tune_args->code_rate = + as102_fe_get_code_rate(params->u.ofdm.code_rate_HP); + } +} + +/* EOF - vim: set textwidth=80 ts=8 sw=8 sts=8 noet: */ |