/* -------------------------------------------------------------------------------------------------- */ /* The LongMynd receiver: stv6120.c */ /* - an implementation of the Serit NIM controlling software for the MiniTiouner Hardware */ /* - the stv6120 (tuner) specific routines */ /* Copyright 2019 Heather Lomond */ /* -------------------------------------------------------------------------------------------------- */ /* This file is part of longmynd. Longmynd 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 3 of the License, or (at your option) any later version. Longmynd 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 longmynd. If not, see . */ /* -------------------------------------------------------------------------------------------------- */ /* ----------------- INCLUDES ----------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------------------- */ #include "math.h" #include #include #include #include "nim.h" #include "stv6120.h" #include "stv6120_regs.h" #include "stv6120_utils.h" #include "errors.h" /* -------------------------------------------------------------------------------------------------- */ /* ----------------- GLOBALS ------------------------------------------------------------------------ */ /* -------------------------------------------------------------------------------------------------- */ /* some globals to make it easier to keep track of register contents across function calls */ uint8_t rdiv; uint8_t ctrl7; uint8_t ctrl8; uint8_t ctrl16; uint8_t ctrl17; /* -------------------------------------------------------------------------------------------------- */ /* ----------------- CONSTANTS ---------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------------------- */ /* from the datasheet: charge pump data */ const uint32_t stv6120_icp_lookup[7][3]={ /* low high icp */ {2380000, 2472000, 0}, {2473000, 2700000, 1}, {2701000, 3021000, 2}, {3022000, 3387000, 3}, {3388000, 3845000, 5}, {3846000, 4394000, 6}, {4395000, 4760000, 7} }; /* a lookup table for the cutuff freq of the HF filter in MHz */ const uint16_t stv6120_cfhf[32]={6796, 5828, 4778, 4118, 3513, 3136, 2794, 2562, 2331, 2169, 2006, 1890, 1771, 1680, 1586, 1514, 1433, 1374, 1310, 1262, 1208, 1167, 1122, 1087, 1049, 1018, 983, 956, 926, 902, 875, 854}; /* -------------------------------------------------------------------------------------------------- */ /* ----------------- ROUTINES ----------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------------------- */ uint8_t stv6120_cal_lowpass(uint8_t tuner) { /* -------------------------------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------------------- */ uint8_t err=ERROR_NONE; uint8_t val; uint16_t timeout; printf("Flow: Tuner cal lowpass\n"); /* turn on the clock for the low pass filter. This is in ctrl7/16 so we have a shadow for it */ if (tuner==TUNER_1) err=stv6120_write_reg(STV6120_CTRL7 , ctrl7 & ~(1 << STV6120_CTRL7_RCCLKOFF_SHIFT)); else err=stv6120_write_reg(STV6120_CTRL16, ctrl16 & ~(1 << STV6120_CTRL7_RCCLKOFF_SHIFT)); /* now we can do a low pass filter calibration, by setting the CALRCSTRT bit. NOte it is safe to just write to it */ if (err==ERROR_NONE) err=stv6120_write_reg(tuner==TUNER_1 ? STV6120_STAT1 : STV6120_STAT2, (STV6120_STAT1_CALRCSTRT_START << STV6120_STAT1_CALRCSTRT_SHIFT)); /* wait for the bit to be cleared to say cal has finished*/ if (err==ERROR_NONE) { timeout=0; do { err=stv6120_read_reg(STV6120_STAT1, &val); timeout++; if (timeout==STV6120_CAL_TIMEOUT) { err=ERROR_TUNER_CAL_LOWPASS_TIMEOUT; printf("ERROR: tuner wait on CAL_lowpass timed out\n"); } } while ((err==ERROR_NONE) && ((val & (1< stv6120_icp_lookup[pos++][1]); icp=stv6120_icp_lookup[pos-1][2]; /* lookup the high freq filter cutoff setting as per datasheet */ cfhf=0; while ((3*freq/1000) <= stv6120_cfhf[cfhf]) { cfhf++; } cfhf--; /* we are sure it isn't greater then the first array element so this is safe */ printf(" Status: tuner:%i, f_vco=0x%x, icp=0x%x, f=0x%x, n=0x%x,\n",tuner,f_vco,icp,f,n); printf(" rdiv=0x%x, p=0x%x, freq=%i, cfhf=%i\n",rdiv,p,freq,stv6120_cfhf[cfhf]); /* now we fill in the PLL and ICP values */ if (err==ERROR_NONE) err=stv6120_write_reg(tuner==TUNER_1 ? STV6120_CTRL3 : STV6120_CTRL12, (n & 0x00ff) ); /* set N[7:0] */ if (err==ERROR_NONE) err=stv6120_write_reg(tuner==TUNER_1 ? STV6120_CTRL4 : STV6120_CTRL13, ((f & 0x0000007f) << 1) | /* set F[6:0] */ ((n & 0x0100) >> 8) ); /* N[8] */ if (err==ERROR_NONE) err=stv6120_write_reg(tuner==TUNER_1 ? STV6120_CTRL5 : STV6120_CTRL14, ((f & 0x00007f80) >> 7) ); /* set F[14:7] */ if (err==ERROR_NONE) err=stv6120_write_reg(tuner==TUNER_1 ? STV6120_CTRL6 : STV6120_CTRL15, ((f & 0x00038000) >> 15) | /* set f[17:15] */ (icp << STV6120_CTRL6_ICP_SHIFT) | /* ICP[2:0] */ STV6120_CTRL6_RESERVED ); /* reserved bit */ if (tuner==TUNER_1) { if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL7, (p<0: the frequency to set tuner 1 to */ /* freq_tuner_2: 0: disable tuner 2 */ /* >0: the frequency to set tuner 2 to */ /* swap: false: use TOP input for tuner_1 */ /* true : use bottom input for tuner_1 */ /* return: error code */ /* -------------------------------------------------------------------------------------------------- */ uint8_t err=ERROR_NONE; uint8_t k; printf("Flow: Tuner init\n"); /* note, we always init the tuner from scratch so no need to check if we have already inited it before */ /* also, the tuner doesn't have much of an ID so no point in checking it */ /* we calculate K from F_xtal/(K+16)=1MHz as specified in the datasheet */ k=NIM_TUNER_XTAL/1000-16; /* setup the clocks for both tuners (note rdiv is a global) */ if (NIM_TUNER_XTAL>=STV6120_RDIV_THRESHOLD) rdiv=1; /* from the data sheet */ else rdiv=0; if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL1, (k << STV6120_CTRL1_K_SHIFT) | (rdiv << STV6120_CTRL1_RDIV_SHIFT) | (STV6120_CTRL1_OSHAPE_SINE << STV6120_CTRL1_OSHAPE_SHIFT) | (STV6120_CTRL1_MCLKDIV_4 << STV6120_CTRL1_MCLKDIV_SHIFT) ); /* Configure path 1 */ if (freq_tuner_1>0) { /* we are go on tuner 1 so turn it on */ if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL2, (STV6120_CTRL2_DCLOOPOFF_ENABLE << STV6120_CTRL2_DCLOOPOFF_SHIFT) | (STV6120_CTRL2_SDOFF_OFF << STV6120_CTRL2_SDOFF_SHIFT) | (STV6120_CTRL2_SYN_ON << STV6120_CTRL2_SYN_SHIFT) | (STV6120_CTRL2_REFOUTSEL_1_25V << STV6120_CTRL2_REFOUTSEL_SHIFT) | (STV6120_CTRL2_BBGAIN_0DB << STV6120_CTRL2_BBGAIN_SHIFT) ); /* CTRL3,4,5,6 are all tuner 1 PLL regs we will set them later */ /* turn off rcclk for now */ if (err==ERROR_NONE) { ctrl7 = (STV6120_CTRL7_RCCLKOFF_DISABLE << STV6120_CTRL7_RCCLKOFF_SHIFT) | (STV6120_CTRL7_CF_5MHZ << STV6120_CTRL7_CF_SHIFT) ; err=stv6120_write_reg(STV6120_CTRL7, ctrl7); } } else { /* we are not going to use path 1 so shut it down */ if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL2, (STV6120_CTRL2_DCLOOPOFF_DISABLE << STV6120_CTRL2_DCLOOPOFF_SHIFT) | (STV6120_CTRL2_SDOFF_ON << STV6120_CTRL2_SDOFF_SHIFT) | (STV6120_CTRL2_SYN_OFF << STV6120_CTRL2_SYN_SHIFT) | (STV6120_CTRL2_REFOUTSEL_1_25V << STV6120_CTRL2_REFOUTSEL_SHIFT) | (STV6120_CTRL2_BBGAIN_0DB << STV6120_CTRL2_BBGAIN_SHIFT) ); /* CTRL3,4,5,6 are all tuner 1 PLL regs we will set them later */ if (err==ERROR_NONE) { ctrl7 = (STV6120_CTRL7_RCCLKOFF_DISABLE << STV6120_CTRL7_RCCLKOFF_SHIFT) | (STV6120_CTRL7_CF_5MHZ << STV6120_CTRL7_CF_SHIFT) ; err=stv6120_write_reg(STV6120_CTRL7, ctrl7); } } /* we need to set tcal for both tuners, but we need to remember the state in case we are using tuner 1 later */ if (err==ERROR_NONE) { ctrl8 = (STV6120_CTRL8_TCAL_DIV_2 << STV6120_CTRL8_TCAL_SHIFT) | (STV6120_CTRL8_CALTIME_500US << STV6120_CTRL8_TCAL_SHIFT) ; err=stv6120_write_reg(STV6120_CTRL8, ctrl8); } /* no need to touch the STAT1 status register for now */ /* setup the RF path registers. RFA and RFD are not used. RFB is fed from the TOP NIM input, RFC from the BOTTOM */ /* if we are swapping these inputs over we need to enable the correct LNAs */ if (swap) { if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL9, (STV6120_CTRL9_RFSEL_RFC_IN << STV6120_CTRL9_RFSEL_1_SHIFT) | (STV6120_CTRL9_RFSEL_RFB_IN << STV6120_CTRL9_RFSEL_2_SHIFT) | STV6120_CTRL9_RESERVED ); /* decide on which LNAs are we going to enable */ if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL10, (( STV6120_CTRL10_LNA_OFF) << STV6120_CTRL10_LNADON_SHIFT) | ((freq_tuner_2 > 0 ? STV6120_CTRL10_LNA_ON : STV6120_CTRL10_LNA_OFF) << STV6120_CTRL10_LNABON_SHIFT) | ((freq_tuner_1 > 0 ? STV6120_CTRL10_LNA_ON : STV6120_CTRL10_LNA_OFF) << STV6120_CTRL10_LNACON_SHIFT) | (( STV6120_CTRL10_LNA_OFF) << STV6120_CTRL10_LNAAON_SHIFT) | ((freq_tuner_2 > 0 ? STV6120_CTRL10_PATH_ON : STV6120_CTRL10_PATH_OFF) << STV6120_CTRL10_PATHON_2_SHIFT) | ((freq_tuner_1 > 0 ? STV6120_CTRL10_PATH_ON : STV6120_CTRL10_PATH_OFF) << STV6120_CTRL10_PATHON_1_SHIFT) ); } else { if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL9, (STV6120_CTRL9_RFSEL_RFB_IN << STV6120_CTRL9_RFSEL_1_SHIFT) | (STV6120_CTRL9_RFSEL_RFC_IN << STV6120_CTRL9_RFSEL_2_SHIFT) | STV6120_CTRL9_RESERVED ); /* decide on which LNAs are we going to enable */ if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL10, (( STV6120_CTRL10_LNA_OFF) << STV6120_CTRL10_LNADON_SHIFT) | ((freq_tuner_2 > 0 ? STV6120_CTRL10_LNA_ON : STV6120_CTRL10_LNA_OFF) << STV6120_CTRL10_LNACON_SHIFT) | ((freq_tuner_1 > 0 ? STV6120_CTRL10_LNA_ON : STV6120_CTRL10_LNA_OFF) << STV6120_CTRL10_LNABON_SHIFT) | (( STV6120_CTRL10_LNA_OFF) << STV6120_CTRL10_LNAAON_SHIFT) | ((freq_tuner_2 > 0 ? STV6120_CTRL10_PATH_ON : STV6120_CTRL10_PATH_OFF) << STV6120_CTRL10_PATHON_2_SHIFT) | ((freq_tuner_1 > 0 ? STV6120_CTRL10_PATH_ON : STV6120_CTRL10_PATH_OFF) << STV6120_CTRL10_PATHON_1_SHIFT) ); } /* Configure path 2 */ if (freq_tuner_2>0) { /* we are go on tuner 2 so turn it on */ if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL11, (STV6120_CTRL2_DCLOOPOFF_ENABLE << STV6120_CTRL2_DCLOOPOFF_SHIFT) | (STV6120_CTRL2_SDOFF_OFF << STV6120_CTRL2_SDOFF_SHIFT) | (STV6120_CTRL2_SYN_ON << STV6120_CTRL2_SYN_SHIFT) | (STV6120_CTRL2_REFOUTSEL_1_25V << STV6120_CTRL2_REFOUTSEL_SHIFT) | (STV6120_CTRL2_BBGAIN_6DB << STV6120_CTRL2_BBGAIN_SHIFT) ); /* CTRL12, 13, 14, 15 are PLL for tuner 2 */ if (err==ERROR_NONE) { ctrl16 = (STV6120_CTRL7_RCCLKOFF_ENABLE << STV6120_CTRL7_RCCLKOFF_SHIFT) | (STV6120_CTRL7_CF_5MHZ << STV6120_CTRL7_CF_SHIFT) ; err=stv6120_write_reg(STV6120_CTRL16, ctrl16); } } else { /* we are not going to use path 1 so shut it down */ if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL11, (STV6120_CTRL2_DCLOOPOFF_DISABLE << STV6120_CTRL2_DCLOOPOFF_SHIFT) | (STV6120_CTRL2_SDOFF_ON << STV6120_CTRL2_SDOFF_SHIFT) | (STV6120_CTRL2_SYN_OFF << STV6120_CTRL2_SYN_SHIFT) | (STV6120_CTRL2_REFOUTSEL_1_25V << STV6120_CTRL2_REFOUTSEL_SHIFT) | (STV6120_CTRL2_BBGAIN_0DB << STV6120_CTRL2_BBGAIN_SHIFT) ); /* CTRL12, 13, 14, 15 are PLL for tuner 2 */ if (err==ERROR_NONE) { ctrl16 = (STV6120_CTRL7_RCCLKOFF_DISABLE << STV6120_CTRL7_RCCLKOFF_SHIFT) | (STV6120_CTRL7_CF_5MHZ << STV6120_CTRL7_CF_SHIFT) ; err=stv6120_write_reg(STV6120_CTRL16, ctrl16); } } /* there is no tcal field in CTRL17 but we still need to remember the state in case we are using tuner 2 later */ if (err==ERROR_NONE) { ctrl17 = (STV6120_CTRL8_CALTIME_500US << STV6120_CTRL8_TCAL_SHIFT); err=stv6120_write_reg(STV6120_CTRL17, ctrl8); } /* no need to touch the STAT2 status register for now */ /* CTRL18, CTRL19 are test regs so just write in the default */ if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL18,STV6120_CTRL18_DEFAULT); if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL19,STV6120_CTRL19_DEFAULT); if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL20, (STV6120_CTRL20_VCOAMP_NORMAL << STV6120_CTRL20_VCOAMP_SHIFT) | STV6120_CTRL20_RESERVED ); /* CTRL21, CTRL22 are test regs so leave alone */ if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL21,STV6120_CTRL21_DEFAULT); if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL22,STV6120_CTRL22_DEFAULT); if (err==ERROR_NONE) err=stv6120_write_reg(STV6120_CTRL23, (STV6120_CTRL20_VCOAMP_NORMAL << STV6120_CTRL20_VCOAMP_SHIFT) | STV6120_CTRL20_RESERVED ); /* now we can calibrate the lowpass filters and setup the PLLs for each tuner required */ if ((err==ERROR_NONE) && (freq_tuner_1>0)) { err=stv6120_cal_lowpass(TUNER_1); if (err==ERROR_NONE) err=stv6120_set_freq(TUNER_1, freq_tuner_1); } if ((err==ERROR_NONE) && (freq_tuner_2>0)) { err=stv6120_cal_lowpass(TUNER_2); if (err==ERROR_NONE) err=stv6120_set_freq(TUNER_2, freq_tuner_2); } if (err!=ERROR_NONE) printf("ERROR: Failed to init Tuner %i, %i\n",freq_tuner_1, freq_tuner_2); return err; } /* -------------------------------------------------------------------------------------------------- */ void stv6120_print_settings() { /* -------------------------------------------------------------------------------------------------- */ /* debug routine to print out all the regsiter values in the tuner */ /* -------------------------------------------------------------------------------------------------- */ uint8_t val; printf("Tuner regs are:\n"); for(int i=0;i<0x19;i++) { stv6120_read_reg(i,&val); printf(" 0x%.2x = 0x%.2x\n",i,val); } }