| /* |
| * Driver for Micronas DRX39xx family (drx3933j) |
| * |
| * Written by Devin Heitmueller <devin.heitmueller@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 of the License, 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/module.h> |
| #include <linux/init.h> |
| #include <linux/string.h> |
| #include <linux/slab.h> |
| |
| #include "dvb_frontend.h" |
| #include "drx39xxj.h" |
| #include "drx_driver.h" |
| #include "bsp_types.h" |
| #include "bsp_tuner.h" |
| #include "drxj_mc.h" |
| #include "drxj.h" |
| |
| static int drx39xxj_set_powerstate(struct dvb_frontend *fe, int enable) |
| { |
| struct drx39xxj_state *state = fe->demodulator_priv; |
| DRXDemodInstance_t *demod = state->demod; |
| DRXStatus_t result; |
| DRXPowerMode_t powerMode; |
| |
| if (enable) |
| powerMode = DRX_POWER_UP; |
| else |
| powerMode = DRX_POWER_DOWN; |
| |
| result = DRX_Ctrl(demod, DRX_CTRL_POWER_MODE, &powerMode); |
| if (result != DRX_STS_OK) { |
| printk("Power state change failed\n"); |
| return 0; |
| } |
| |
| state->powered_up = enable; |
| return 0; |
| } |
| |
| static int drx39xxj_read_status(struct dvb_frontend *fe, fe_status_t * status) |
| { |
| struct drx39xxj_state *state = fe->demodulator_priv; |
| DRXDemodInstance_t *demod = state->demod; |
| DRXStatus_t result; |
| DRXLockStatus_t lock_status; |
| |
| *status = 0; |
| |
| result = DRX_Ctrl(demod, DRX_CTRL_LOCK_STATUS, &lock_status); |
| if (result != DRX_STS_OK) { |
| printk("drx39xxj: could not get lock status!\n"); |
| *status = 0; |
| } |
| |
| switch (lock_status) { |
| case DRX_NEVER_LOCK: |
| *status = 0; |
| printk("drx says NEVER_LOCK\n"); |
| break; |
| case DRX_NOT_LOCKED: |
| *status = 0; |
| break; |
| case DRX_LOCK_STATE_1: |
| case DRX_LOCK_STATE_2: |
| case DRX_LOCK_STATE_3: |
| case DRX_LOCK_STATE_4: |
| case DRX_LOCK_STATE_5: |
| case DRX_LOCK_STATE_6: |
| case DRX_LOCK_STATE_7: |
| case DRX_LOCK_STATE_8: |
| case DRX_LOCK_STATE_9: |
| *status = FE_HAS_SIGNAL |
| | FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC; |
| break; |
| case DRX_LOCKED: |
| *status = FE_HAS_SIGNAL |
| | FE_HAS_CARRIER |
| | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; |
| break; |
| default: |
| printk("Lock state unknown %d\n", lock_status); |
| } |
| |
| return 0; |
| } |
| |
| static int drx39xxj_read_ber(struct dvb_frontend *fe, u32 * ber) |
| { |
| struct drx39xxj_state *state = fe->demodulator_priv; |
| DRXDemodInstance_t *demod = state->demod; |
| DRXStatus_t result; |
| DRXSigQuality_t sig_quality; |
| |
| result = DRX_Ctrl(demod, DRX_CTRL_SIG_QUALITY, &sig_quality); |
| if (result != DRX_STS_OK) { |
| printk("drx39xxj: could not get ber!\n"); |
| *ber = 0; |
| return 0; |
| } |
| |
| *ber = sig_quality.postReedSolomonBER; |
| return 0; |
| } |
| |
| static int drx39xxj_read_signal_strength(struct dvb_frontend *fe, |
| u16 * strength) |
| { |
| struct drx39xxj_state *state = fe->demodulator_priv; |
| DRXDemodInstance_t *demod = state->demod; |
| DRXStatus_t result; |
| DRXSigQuality_t sig_quality; |
| |
| result = DRX_Ctrl(demod, DRX_CTRL_SIG_QUALITY, &sig_quality); |
| if (result != DRX_STS_OK) { |
| printk("drx39xxj: could not get signal strength!\n"); |
| *strength = 0; |
| return 0; |
| } |
| |
| /* 1-100% scaled to 0-65535 */ |
| *strength = (sig_quality.indicator * 65535 / 100); |
| return 0; |
| } |
| |
| static int drx39xxj_read_snr(struct dvb_frontend *fe, u16 * snr) |
| { |
| struct drx39xxj_state *state = fe->demodulator_priv; |
| DRXDemodInstance_t *demod = state->demod; |
| DRXStatus_t result; |
| DRXSigQuality_t sig_quality; |
| |
| result = DRX_Ctrl(demod, DRX_CTRL_SIG_QUALITY, &sig_quality); |
| if (result != DRX_STS_OK) { |
| printk("drx39xxj: could not read snr!\n"); |
| *snr = 0; |
| return 0; |
| } |
| |
| *snr = sig_quality.MER; |
| return 0; |
| } |
| |
| static int drx39xxj_read_ucblocks(struct dvb_frontend *fe, u32 * ucblocks) |
| { |
| struct drx39xxj_state *state = fe->demodulator_priv; |
| DRXDemodInstance_t *demod = state->demod; |
| DRXStatus_t result; |
| DRXSigQuality_t sig_quality; |
| |
| result = DRX_Ctrl(demod, DRX_CTRL_SIG_QUALITY, &sig_quality); |
| if (result != DRX_STS_OK) { |
| printk("drx39xxj: could not get uc blocks!\n"); |
| *ucblocks = 0; |
| return 0; |
| } |
| |
| *ucblocks = sig_quality.packetError; |
| return 0; |
| } |
| |
| static int drx39xxj_set_frontend(struct dvb_frontend *fe) |
| { |
| #ifdef DJH_DEBUG |
| int i; |
| #endif |
| struct dtv_frontend_properties *p = &fe->dtv_property_cache; |
| struct drx39xxj_state *state = fe->demodulator_priv; |
| DRXDemodInstance_t *demod = state->demod; |
| DRXStandard_t standard = DRX_STANDARD_8VSB; |
| DRXChannel_t channel; |
| DRXStatus_t result; |
| DRXUIOData_t uioData; |
| DRXChannel_t defChannel = { /* frequency */ 0, |
| /* bandwidth */ DRX_BANDWIDTH_6MHZ, |
| /* mirror */ DRX_MIRROR_NO, |
| /* constellation */ DRX_CONSTELLATION_AUTO, |
| /* hierarchy */ DRX_HIERARCHY_UNKNOWN, |
| /* priority */ DRX_PRIORITY_UNKNOWN, |
| /* coderate */ DRX_CODERATE_UNKNOWN, |
| /* guard */ DRX_GUARD_UNKNOWN, |
| /* fftmode */ DRX_FFTMODE_UNKNOWN, |
| /* classification */ DRX_CLASSIFICATION_AUTO, |
| /* symbolrate */ 5057000, |
| /* interleavemode */ DRX_INTERLEAVEMODE_UNKNOWN, |
| /* ldpc */ DRX_LDPC_UNKNOWN, |
| /* carrier */ DRX_CARRIER_UNKNOWN, |
| /* frame mode */ DRX_FRAMEMODE_UNKNOWN |
| }; |
| |
| /* Bring the demod out of sleep */ |
| drx39xxj_set_powerstate(fe, 1); |
| |
| /* Now make the tuner do it's thing... */ |
| if (fe->ops.tuner_ops.set_params) { |
| if (fe->ops.i2c_gate_ctrl) |
| fe->ops.i2c_gate_ctrl(fe, 1); |
| fe->ops.tuner_ops.set_params(fe); |
| if (fe->ops.i2c_gate_ctrl) |
| fe->ops.i2c_gate_ctrl(fe, 0); |
| } |
| |
| if (standard != state->current_standard || state->powered_up == 0) { |
| /* Set the standard (will be powered up if necessary */ |
| result = DRX_Ctrl(demod, DRX_CTRL_SET_STANDARD, &standard); |
| if (result != DRX_STS_OK) { |
| printk("Failed to set standard! result=%02x\n", result); |
| return -EINVAL; |
| } |
| state->powered_up = 1; |
| state->current_standard = standard; |
| } |
| |
| /* set channel parameters */ |
| channel = defChannel; |
| channel.frequency = p->frequency / 1000; |
| channel.bandwidth = DRX_BANDWIDTH_6MHZ; |
| channel.constellation = DRX_CONSTELLATION_AUTO; |
| |
| /* program channel */ |
| result = DRX_Ctrl(demod, DRX_CTRL_SET_CHANNEL, &channel); |
| if (result != DRX_STS_OK) { |
| printk("Failed to set channel!\n"); |
| return -EINVAL; |
| } |
| // Just for giggles, let's shut off the LNA again.... |
| uioData.uio = DRX_UIO1; |
| uioData.value = FALSE; |
| result = DRX_Ctrl(demod, DRX_CTRL_UIO_WRITE, &uioData); |
| if (result != DRX_STS_OK) { |
| printk("Failed to disable LNA!\n"); |
| return 0; |
| } |
| #ifdef DJH_DEBUG |
| for (i = 0; i < 2000; i++) { |
| fe_status_t status; |
| drx39xxj_read_status(fe, &status); |
| printk("i=%d status=%d\n", i, status); |
| msleep(100); |
| i += 100; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int drx39xxj_sleep(struct dvb_frontend *fe) |
| { |
| /* power-down the demodulator */ |
| return drx39xxj_set_powerstate(fe, 0); |
| } |
| |
| static int drx39xxj_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) |
| { |
| struct drx39xxj_state *state = fe->demodulator_priv; |
| DRXDemodInstance_t *demod = state->demod; |
| Bool_t i2c_gate_state; |
| DRXStatus_t result; |
| |
| #ifdef DJH_DEBUG |
| printk("i2c gate call: enable=%d state=%d\n", enable, |
| state->i2c_gate_open); |
| #endif |
| |
| if (enable) |
| i2c_gate_state = TRUE; |
| else |
| i2c_gate_state = FALSE; |
| |
| if (state->i2c_gate_open == enable) { |
| /* We're already in the desired state */ |
| return 0; |
| } |
| |
| result = DRX_Ctrl(demod, DRX_CTRL_I2C_BRIDGE, &i2c_gate_state); |
| if (result != DRX_STS_OK) { |
| printk("drx39xxj: could not open i2c gate [%d]\n", result); |
| dump_stack(); |
| } else { |
| state->i2c_gate_open = enable; |
| } |
| return 0; |
| } |
| |
| static int drx39xxj_init(struct dvb_frontend *fe) |
| { |
| /* Bring the demod out of sleep */ |
| drx39xxj_set_powerstate(fe, 1); |
| |
| return 0; |
| } |
| |
| static int drx39xxj_get_tune_settings(struct dvb_frontend *fe, |
| struct dvb_frontend_tune_settings *tune) |
| { |
| tune->min_delay_ms = 1000; |
| return 0; |
| } |
| |
| static void drx39xxj_release(struct dvb_frontend *fe) |
| { |
| struct drx39xxj_state *state = fe->demodulator_priv; |
| kfree(state); |
| } |
| |
| static struct dvb_frontend_ops drx39xxj_ops; |
| |
| struct dvb_frontend *drx39xxj_attach(struct i2c_adapter *i2c) |
| { |
| struct drx39xxj_state *state = NULL; |
| |
| I2CDeviceAddr_t *demodAddr = NULL; |
| DRXCommonAttr_t *demodCommAttr = NULL; |
| DRXJData_t *demodExtAttr = NULL; |
| DRXDemodInstance_t *demod = NULL; |
| DRXUIOCfg_t uioCfg; |
| DRXUIOData_t uioData; |
| DRXStatus_t result; |
| |
| /* allocate memory for the internal state */ |
| state = kmalloc(sizeof(struct drx39xxj_state), GFP_KERNEL); |
| if (state == NULL) |
| goto error; |
| |
| demod = kmalloc(sizeof(DRXDemodInstance_t), GFP_KERNEL); |
| if (demod == NULL) |
| goto error; |
| |
| demodAddr = kmalloc(sizeof(I2CDeviceAddr_t), GFP_KERNEL); |
| if (demodAddr == NULL) |
| goto error; |
| |
| demodCommAttr = kmalloc(sizeof(DRXCommonAttr_t), GFP_KERNEL); |
| if (demodCommAttr == NULL) |
| goto error; |
| |
| demodExtAttr = kmalloc(sizeof(DRXJData_t), GFP_KERNEL); |
| if (demodExtAttr == NULL) |
| goto error; |
| |
| /* setup the state */ |
| state->i2c = i2c; |
| state->demod = demod; |
| |
| memcpy(demod, &DRXJDefaultDemod_g, sizeof(DRXDemodInstance_t)); |
| |
| demod->myI2CDevAddr = demodAddr; |
| memcpy(demod->myI2CDevAddr, &DRXJDefaultAddr_g, |
| sizeof(I2CDeviceAddr_t)); |
| demod->myI2CDevAddr->userData = state; |
| demod->myCommonAttr = demodCommAttr; |
| memcpy(demod->myCommonAttr, &DRXJDefaultCommAttr_g, |
| sizeof(DRXCommonAttr_t)); |
| demod->myCommonAttr->microcode = DRXJ_MC_MAIN; |
| // demod->myCommonAttr->verifyMicrocode = FALSE; |
| demod->myCommonAttr->verifyMicrocode = TRUE; |
| demod->myCommonAttr->intermediateFreq = 5000; |
| |
| demod->myExtAttr = demodExtAttr; |
| memcpy(demod->myExtAttr, &DRXJData_g, sizeof(DRXJData_t)); |
| ((DRXJData_t *) demod->myExtAttr)->uioSmaTxMode = |
| DRX_UIO_MODE_READWRITE; |
| |
| demod->myTuner = NULL; |
| |
| result = DRX_Open(demod); |
| if (result != DRX_STS_OK) { |
| printk("DRX open failed! Aborting\n"); |
| kfree(state); |
| return NULL; |
| } |
| |
| /* Turn off the LNA */ |
| uioCfg.uio = DRX_UIO1; |
| uioCfg.mode = DRX_UIO_MODE_READWRITE; |
| /* Configure user-I/O #3: enable read/write */ |
| result = DRX_Ctrl(demod, DRX_CTRL_UIO_CFG, &uioCfg); |
| if (result != DRX_STS_OK) { |
| printk("Failed to setup LNA GPIO!\n"); |
| return NULL; |
| } |
| |
| uioData.uio = DRX_UIO1; |
| uioData.value = FALSE; |
| result = DRX_Ctrl(demod, DRX_CTRL_UIO_WRITE, &uioData); |
| if (result != DRX_STS_OK) { |
| printk("Failed to disable LNA!\n"); |
| return NULL; |
| } |
| |
| /* create dvb_frontend */ |
| memcpy(&state->frontend.ops, &drx39xxj_ops, |
| sizeof(struct dvb_frontend_ops)); |
| |
| state->frontend.demodulator_priv = state; |
| return &state->frontend; |
| |
| error: |
| if (state != NULL) |
| kfree(state); |
| if (demod != NULL) |
| kfree(demod); |
| return NULL; |
| } |
| |
| static struct dvb_frontend_ops drx39xxj_ops = { |
| .delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B }, |
| .info = { |
| .name = "Micronas DRX39xxj family Frontend", |
| .frequency_stepsize = 62500, |
| .frequency_min = 51000000, |
| .frequency_max = 858000000, |
| .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB}, |
| |
| .init = drx39xxj_init, |
| .i2c_gate_ctrl = drx39xxj_i2c_gate_ctrl, |
| .sleep = drx39xxj_sleep, |
| .set_frontend = drx39xxj_set_frontend, |
| .get_tune_settings = drx39xxj_get_tune_settings, |
| .read_status = drx39xxj_read_status, |
| .read_ber = drx39xxj_read_ber, |
| .read_signal_strength = drx39xxj_read_signal_strength, |
| .read_snr = drx39xxj_read_snr, |
| .read_ucblocks = drx39xxj_read_ucblocks, |
| .release = drx39xxj_release, |
| }; |
| |
| MODULE_DESCRIPTION("Micronas DRX39xxj Frontend"); |
| MODULE_AUTHOR("Devin Heitmueller"); |
| MODULE_LICENSE("GPL"); |
| |
| EXPORT_SYMBOL(drx39xxj_attach); |