#ifdef HAVE_XORG_CONFIG_H #include #endif #include #include #include "xf86.h" #include "xf86i2c.h" #include "msp3430.h" #include "i2c_def.h" #define CONTROL 0x00 #define WR_DEM 0x10 #define RD_DEM 0x11 #define WR_DSP 0x12 #define RD_DSP 0x13 void InitMSP34xxG(MSP3430Ptr m); void InitMSP34x5D(MSP3430Ptr m); void CheckModeMSP34x5D(MSP3430Ptr m); char *MSP_getProductName (CARD16 product_id); void mpause(int milliseconds); #define __MSPDEBUG__ 0 #if __MSPDEBUG__ > 3 void MSPBeep(MSP3430Ptr m, CARD8 freq); #define __MSPBEEP MSPBeep(m,0x14); #else #define __MSPBEEP #endif static void SetMSP3430Control(MSP3430Ptr m, CARD8 RegAddress, CARD8 RegValueHigh, CARD8 RegValueLow) { I2CByte data[3]; data[0]=RegAddress; data[1]=RegValueHigh; data[2]=RegValueLow; I2C_WriteRead(&(m->d),data,3,NULL,0); } static void SetMSP3430Data(MSP3430Ptr m, CARD8 RegAddress, CARD8 RegSubAddressHigh, CARD8 RegSubAddressLow, CARD8 RegValueHigh, CARD8 RegValueLow) { I2CByte data[5]; #ifdef MSP_DEBUG if(!m->registers_present[RegSubAddressLow]){ xf86DrvMsg(m->d.pI2CBus->scrnIndex,X_ERROR, "Attempt to access non-existent register in MSP34xxX: 0x%02x 0x%02x 0x%02x <- 0x%02x 0x%02x\n", RegAddress, RegSubAddressHigh, RegSubAddressLow, RegValueHigh, RegValueLow); } #endif data[0] = RegAddress; data[1] = RegSubAddressHigh; data[2] = RegSubAddressLow; data[3] = RegValueHigh; data[4] = RegValueLow; I2C_WriteRead(&(m->d),data,5,NULL,0); } static void GetMSP3430Data(MSP3430Ptr m, CARD8 RegAddress, CARD8 RegSubAddressHigh, CARD8 RegSubAddressLow, CARD8 *RegValueHigh, CARD8 *RegValueLow) { I2CByte send[3]; I2CByte receive[2]; send[0] = RegAddress; send[1] = RegSubAddressHigh; send[2] = RegSubAddressLow; I2C_WriteRead(&(m->d), send, 3, receive, 2); *RegValueHigh = receive[0]; *RegValueLow = receive[1]; } #if __MSPDEBUG__ > 2 static void MSP3430DumpStatus(MSP3430Ptr m) { CARD8 status_hi, status_lo; CARD8 subaddr, data[2]; GetMSP3430Data(m, RD_DEM, 0x02, 0x00, &status_hi, &status_lo); xf86DrvMsg(m->d.pI2CBus->scrnIndex, X_INFO, "MSP34xx: SAP(8)=%d mono/NICAM(7)=%d stereo=%d %s O_1=%d O_0=%d 2nd car=%d 1st car=%d\n", status_hi & 1, (status_lo>>7) & 1, (status_lo>>6)&1, (status_lo>>5)? ( (status_hi>>1)&1? "bad NICAM reception" : "NICAM" ) : ((status_hi>>1)&1 ? "bogus" : "ANALOG FM/AM") , (status_lo>>4)&1, (status_lo>>3)&1,!( (status_lo>>2)&1), !((status_lo>>1)&1)); GetMSP3430Data(m, RD_DEM, 0x00, 0x7E, &status_hi, &status_lo); xf86DrvMsg(m->d.pI2CBus->scrnIndex, X_INFO, "MSP34xx: standard result=0x%02x%02x\n", status_hi, status_lo); subaddr=0x0; I2C_WriteRead(&(m->d), &subaddr, 1, data, 2); xf86DrvMsg(m->d.pI2CBus->scrnIndex, X_INFO, "MSP34xx: control=0x%02x%02x\n", data[1], data[0]); } #endif /* wrapper */ void InitMSP3430(MSP3430Ptr m) { #if __MSPDEBUG__ > 1 xf86DrvMsg(m->d.pI2CBus->scrnIndex,X_INFO,"InitMSP3430(m->connector=%d, m->standard=%d, m->chip_family=%d)\n", m->connector, m->standard, m->chip_family); #endif switch (m->chip_family) { case MSPFAMILY_34x0G: InitMSP34xxG(m); break; case MSPFAMILY_34x5G: InitMSP34xxG(m); break; case MSPFAMILY_34x5D: InitMSP34x5D(m); break; } } /*----------------------------------------------------------------- | common functions for all MSP34xx chips |----------------------------------------------------------------*/ MSP3430Ptr DetectMSP3430(I2CBusPtr b, I2CSlaveAddr addr) { MSP3430Ptr m; I2CByte a; CARD8 hardware_version, major_revision, product_code, rom_version; Bool supported; m = calloc(1,sizeof(MSP3430Rec)); if(m == NULL)return NULL; m->d.DevName = strdup("MSP34xx"); m->d.SlaveAddr = addr; m->d.pI2CBus = b; m->d.NextDev = NULL; m->d.StartTimeout = b->StartTimeout; m->d.BitTimeout = b->BitTimeout; m->d.AcknTimeout = b->AcknTimeout; m->d.ByteTimeout = b->ByteTimeout; if(!I2C_WriteRead(&(m->d), NULL, 0, &a, 1)) { free(__UNCONST(m->d.DevName)); free(m); return NULL; } m->standard=MSP3430_NTSC; m->connector=MSP3430_CONNECTOR_1; m->mode=MSPMODE_STEREO_A; /*stereo or chanel A if avail. */ m->c_format=MSPFORMAT_UNKNOWN; m->c_standard=MSPSTANDARD_UNKNOWN; m->c_matrix=m->c_fmmatrix=m->c_source=0; m->volume=0; m->recheck=FALSE; GetMSP3430Data(m, RD_DSP, 0x00, 0x1E, &hardware_version, &major_revision); GetMSP3430Data(m, RD_DSP, 0x00, 0x1F, &product_code, &rom_version); m->hardware_version=hardware_version; m->major_revision=major_revision; m->product_code=product_code; m->rom_version=rom_version; m->chip_id=((major_revision << 8) | product_code); supported=FALSE; switch (major_revision) { case 4: /* 34xxD */ switch (product_code) { case 0x05: /* 3405D */ case 0x0A: /* 3410D */ case 0x0F: /* 3415D */ m->chip_family=MSPFAMILY_34x5D; m->recheck=TRUE; supported=TRUE; break; default: m->chip_family=MSPFAMILY_34x0D; } break; case 7: /* 34xxG */ switch(product_code){ case 0x00: case 0x0A: case 0x1E: case 0x28: case 0x32: m->chip_family=MSPFAMILY_34x0G; supported=TRUE; break; case 0x0f: case 0x19: case 0x2d: case 0x37: case 0x41: m->chip_family=MSPFAMILY_34x5G; supported=TRUE; #ifdef MSP_DEBUG memset(m->registers_present, 0, 256); #define A(num) m->registers_present[(num)]=1; #define B(num1, num2) memset(&(m->registers_present[num1]), 1, num2-num1); A(0x20) A(0x30) A(0x40) A(0x00) B(0x01, 0x08) B(0x0B, 0x0E) A(0x10) B(0x12,0x14) A(0x16) A(0x29) #undef B #undef A #endif break; default: m->chip_family=MSPFAMILY_UNKNOWN; } break; default: m->chip_family=MSPFAMILY_UNKNOWN; } xf86DrvMsg(m->d.pI2CBus->scrnIndex, X_INFO, "Found %s%s, rom version 0x%02x, chip_id=0x%04x\n", MSP_getProductName(m->chip_id), supported?"":" (unsupported)", rom_version, m->chip_id); if (!supported) { free(__UNCONST(m->d.DevName)); free(m); return NULL; } if(!I2CDevInit(&(m->d))) { free(__UNCONST(m->d.DevName)); free(m); return NULL; } return m; } void ResetMSP3430(MSP3430Ptr m) { /* Reset the MSP3430 */ SetMSP3430Control(m, 0x00, 0x80, 0x00); /* Set it back to normal operation */ SetMSP3430Control(m, 0x00, 0x00, 0x00); m->c_format=MSPFORMAT_UNKNOWN; m->c_standard=MSPSTANDARD_UNKNOWN; m->c_matrix=m->c_fmmatrix=m->c_source=0; m->volume=0; } void MSP3430SetVolume (MSP3430Ptr m, CARD8 value) { CARD8 result; #if 0 CARD8 old_volume; GetMSP3430Data(m, RD_DSP, 0x00, 0x00, &old_volume, &result); xf86DrvMsg(m->d.pI2CBus->scrnIndex, X_INFO, "MSP3430 result 0x%02x\n", result); #endif /* save an extra Get call */ result=0; SetMSP3430Data(m, WR_DSP, 0x00, 0x00, value, result); SetMSP3430Data(m, WR_DSP, 0x00, 0x07, value, 0); m->volume=value; #if __MSPDEBUG__ > 2 MSP3430DumpStatus(m); __MSPBEEP GetMSP3430Data(m, RD_DSP, 0x00, 0x00, &old_volume, &result); xf86DrvMsg(m->d.pI2CBus->scrnIndex, X_INFO, "MSP3430 volume 0x%02x\n",value); #endif } void MSP3430SetSAP (MSP3430Ptr m, int mode) { xf86DrvMsg(m->d.pI2CBus->scrnIndex, X_INFO, "Put actual code to change SAP here\n"); SetMSP3430Data(m, WR_DSP, 0x00, 0x08, mode & 0xff, 0x20); } #if 0 void MSP3430SetSource(MSP3430Ptr m, CARD8 value) { /* Write to DSP, register 0x0008, (loudspeaker channel source/matrix) */ /* This sets the source to the TV tuner, for stereo operation */ SetMSP3430Data(m, WR_DSP, 0x00, 0x08, value, 0x20); } #endif char *MSP_getProductName (CARD16 product_id) { switch (product_id) { case 0x0400: return "MSP3400D"; case 0x040a: return "MSP3410D"; case 0x0405: return "MSP3405D"; case 0x040f: return "MSP3415D"; case 0x0700: return "MSP3400G"; case 0x070a: return "MSP3410G"; case 0x071e: return "MSP3430G"; case 0x0728: return "MSP3440G"; case 0x0732: return "MSP3450G"; case 0x070f: return "MSP3415G"; case 0x0719: return "MSP3425G"; case 0x072d: return "MSP3445G"; case 0x0737: return "MSP3455G"; case 0x0741: return "MSP3465G"; } return "MSP - unknown type"; } #if __MSPDEBUG__ > 2 /*puts beep in MSP output freq = 0x01 - 16Hz ... 0x40 - 1kHz ... 0xff - 4kHz */ void MSPBeep(MSP3430Ptr m, CARD8 freq) { SetMSP3430Data (m, WR_DSP, 0x00, freq, 0x7f, 0x40); mpause(100); SetMSP3430Data (m, WR_DSP, 0x00, 0x14, 0x00, 0x00); } #endif void mpause(int milliseconds) { int i,m; m=milliseconds/20; for (i=0;i 1 xf86DrvMsg(m->d.pI2CBus->scrnIndex,X_INFO,"InitMSP34xxG(m->connector=%d, m->standard=%d, m->chip_family=%d)\n", m->connector, m->standard, m->chip_family); #endif /* Reset MSP3430 */ SetMSP3430Control(m, 0x00, 0x80, 0x00); /* Set it back to normal operation */ SetMSP3430Control(m, 0x00, 0x00, 0x00); /*set MODUS register */ /* bits: 0 - automatic sound detection */ /* 1 - enable STATUS change */ /* 12 - detect 6.5 Mhz carrier as D/K1, D/K2 or D/K NICAM (does not seem to work ) */ /* 13 - detect 4.5 Mhz carrier as BTSC */ if ( (m->standard & 0xff) == MSP3430_PAL ) { SetMSP3430Data(m, WR_DEM, 0x00, 0x30, 0x30, 0x03|0x08); /* make O_ pins tristate */ /* PAL standard */ SetMSP3430Data(m, WR_DEM, 0x00, 0x20, 0x00, 0x01); /* possibly wrong */ } else { SetMSP3430Data(m, WR_DEM, 0x00, 0x30, 0x20, 0x03|0x08); /* standard selection is M-BTSC-Stereo */ SetMSP3430Data(m, WR_DEM, 0x00, 0x20, 0x00, 0x20); } switch(m->connector){ case MSP3430_CONNECTOR_1: SetMSP3430Data(m, WR_DSP, 0x00, 0x08, 0x03, 0x20); break; case MSP3430_CONNECTOR_2: /* this has not been checked yet.. could be bogus */ /* SCART Input Prescale: 0 dB gain */ SetMSP3430Data(m, WR_DSP, 0x00, 0x0d, 0x19, 0x00); SetMSP3430Data(m, WR_DSP, 0x00, 0x08, 0x02, 0x20); break; case MSP3430_CONNECTOR_3: default: /* SCART Input Prescale: 0 dB gain */ SetMSP3430Data(m, WR_DSP, 0x00, 0x0d, 0x19, 0x00); SetMSP3430Data(m, WR_DSP, 0x00, 0x08, 0x02, 0x20); break; } switch(m->standard){ case MSP3430_PAL: SetMSP3430Data(m, WR_DSP, 0x00, 0x0e, 0x24, 0x03); SetMSP3430Data(m, WR_DSP, 0x00, 0x10, 0x00, 0x5a); SetMSP3430Data(m, WR_DEM, 0x00, 0x20, 0x00, 0x03); /* Set volume to FAST_MUTE. */ SetMSP3430Data(m, WR_DSP, 0x00, 0x00, 0xFF, 0x00); break; case MSP3430_PAL_DK1: SetMSP3430Data(m, WR_DSP, 0x00, 0x0e, 0x24, 0x03); SetMSP3430Data(m, WR_DSP, 0x00, 0x10, 0x00, 0x5a); SetMSP3430Data(m, WR_DEM, 0x00, 0x20, 0x00, 0x04); /* Set volume to FAST_MUTE. */ SetMSP3430Data(m, WR_DSP, 0x00, 0x00, 0xFF, 0x00); break; case MSP3430_SECAM: /* is this right ? */ case MSP3430_NTSC: /* Write to DSP, register 0x000E, (prescale FM/FM matrix) */ SetMSP3430Data(m, WR_DSP, 0x00, 0x0e, 0x24, 0x03); /* Set volume to FAST_MUTE. */ SetMSP3430Data(m, WR_DSP, 0x00, 0x00, 0xFF, 0x00); break; } } /*----------------------------------------------------------------- | specific functions for all MSP34x5D chips |----------------------------------------------------------------*/ void InitMSP34x5D(MSP3430Ptr m) { int count; CARD8 high,low; CARD16 result,standard; CARD16 peak; if (m->c_format==MSPFORMAT_UNKNOWN) ResetMSP3430(m); else { /*mute volume*/ SetMSP3430Data (m, WR_DSP, 0x00, 0x00, 0x00, 0x00); } switch(m->connector){ case MSP3430_CONNECTOR_2: case MSP3430_CONNECTOR_3: if (m->c_format!=MSPFORMAT_SCART) { /* SCART Input Prescale: 0 dB gain */ SetMSP3430Data (m, WR_DSP, 0x00, 0x0d, 0x19, 0x00); /* this has not been checked yet.. could be bogus */ m->c_format=MSPFORMAT_SCART; /*stereo*/ } break; case MSP3430_CONNECTOR_1: default: switch ( m->standard & 0x00ff ) { case MSP3430_PAL: switch( m->standard ) { case MSP3430_PAL_DK1: standard=MSPSTANDARD_FM_DK1; break; /* case MSP3430_PAL_DK2: standard=MSPSTANDARD_FM_DK2; break; case MSP3430_PAL_BG: may be FM stereo (Germany) or FM NICAM (Scandinavia,spain) standard=MSPSTANDARD_AUTO; break; */ default: standard=MSPSTANDARD_AUTO; } break; case MSP3430_SECAM: standard=MSPSTANDARD_AUTO; case MSP3430_NTSC: /* Only MSP34x5 supported format - Korean NTSC-M*/ standard=MSPSTANDARD_FM_M; default: standard=MSPSTANDARD_AUTO; } /*no NICAM support in MSP3410D - force to autodetect*/ if ((m->chip_id==0x405) && (standard>=MSPSTANDARD_NICAM_BG)) standard=MSPSTANDARD_AUTO; if (m->c_standard != standard) { SetMSP3430Data (m, WR_DEM, 0x00, 0x20, standard>>8, standard & 0xFF); if (standard==MSPSTANDARD_AUTO) { count = 50; /* time shouldn't exceed 1s, just in case */ do { usleep(20000); GetMSP3430Data (m, RD_DEM, 0x00, 0x7e, &high, &low); result = ( high << 8 ) | low; --count; } while( result > 0x07ff && count > 0 ); if ((result > MSPSTANDARD_AUTO)) standard=result; else standard=MSPSTANDARD_UNKNOWN; #if __MSPDEBUG__ > 1 xf86DrvMsg(m->d.pI2CBus->scrnIndex,X_INFO,"Detected audio standard: %d\n",result); #endif /* result = MSPSTANDARD_NICAM_L can be one of: SECAM_L - MSPSTANDARD_NICAM_L D/K1 - MSPSTANDARD_FM_DK1 D/K2 - MSPSTANDARD_FM_DK2 D/K-NICAM - MSPSTANDARD_NICAM_DK*/ if( standard == MSPSTANDARD_NICAM_L ) { if ((m->standard & 0x00ff)==MSP3430_PAL) { /* force PAL D/K */ standard=MSPSTANDARD_FM_DK1; SetMSP3430Data (m, WR_DEM, 0x00, 0x20, standard>>8, standard & 0xFF); #if __MSPDEBUG__ > 1 xf86DrvMsg(m->d.pI2CBus->scrnIndex,X_INFO, "Detected 6.5MHz carrier - forced to D/K1 !!!\n" ); #endif } } } m->c_standard=standard; } /*end - standard changed*/ else { if (standardc_format=MSPFORMAT_1xFM; } else if (standardL ch1->R*/ SetMSP3430Data (m, WR_DSP, 0x00, 0x0c, 0x00, 0x20); mpause(250); GetMSP3430Data (m, RD_DSP, 0x00, 0x1A, &high, &low); peak = (high << 8) | low; #if __MSPDEBUG__ > 1 xf86DrvMsg(m->d.pI2CBus->scrnIndex,X_INFO,"Second carrier Quasi-Peak detection: %d\n",peak); #endif /*turn on FM DC Notch*/ SetMSP3430Data (m, WR_DSP, 0x00, 0x17, 0x00, 0x00); if (peak<5) { /* if second carrier not detected - only mono from first carrier*/ m->c_format=MSPFORMAT_1xFM; } else { m->c_format=MSPFORMAT_2xFM; /*start of FM identification process - FM_WAIT wait at least 0.5s - used 1s - gives beter resolution*/ mpause(1000); } } else { if (standard==MSPSTANDARD_NICAM_L) { m->c_format=MSPFORMAT_NICAM_AM; /* set AM prescale */ SetMSP3430Data (m, WR_DSP, 0x00, 0x0e, 0x7C, 0); } else { m->c_format=MSPFORMAT_NICAM_FM; /* set FM prescale */ SetMSP3430Data (m, WR_DSP, 0x00, 0x0e, 0x30, 0); } /* set FM deemphasis*/ SetMSP3430Data (m, WR_DSP, 0x00, 0x0f, 0x00, 0); /* set NICAM prescale to 0dB */ SetMSP3430Data (m, WR_DSP, 0x00, 0x10, 0x20, 0); } break; } /*end - case conector*/ CheckModeMSP34x5D(m); /* Set volume to FAST_MUTE. */ /*SetMSP3430Data(m, WR_DSP, 0x00, 0x00, 0xFF, 0x00);*/ /*set volume*/ MSP3430SetVolume(m,m->volume); __MSPBEEP } /* EnableMSP34x5D ()... */ void CheckModeMSP34x5D(MSP3430Ptr m) { const char stereo_on=25; const char stereo_off=20; const char dual_on=-stereo_on; const char dual_off=-stereo_off; char detect; CARD8 matrix, fmmatrix, source, high, low; fmmatrix=0; /*no matrix*/ source=0; /*FM*/ switch (m->c_format) { case MSPFORMAT_NICAM_FM: case MSPFORMAT_NICAM_AM: case MSPFORMAT_SCART: source=( (m->c_format == MSPFORMAT_SCART)?2:1 ); switch (m->mode) { case MSPMODE_MONO: matrix=0x30; /*MONO*/ break; case MSPMODE_A: matrix=0x00; /*A*/ break; case MSPMODE_B: matrix=0x10; /*B*/ break; default: matrix=0x20; /*STEREO*/ break; } break; default: case MSPFORMAT_1xFM: matrix=0x00; /*A*/ break; case MSPFORMAT_2xFM: switch (m->mode) { case MSPMODE_MONO: matrix=0x30; /*MONO*/ break; case MSPMODE_STEREO: matrix=0x20; /*STEREO*/ fmmatrix=((m->c_standard==MSPSTANDARD_FM_M)?2:1); break; case MSPMODE_AB: matrix=0x20; /*STEREO*/ break; case MSPMODE_A: matrix=0x00; /*A*/ break; case MSPMODE_B: matrix=0x10; /*B*/ break; default: /*FM_IDENT_CHECK*/ GetMSP3430Data (m, RD_DSP, 0x00, 0x18, &high, &low); detect=(char)high; #if __MSPDEBUG__ > 1 xf86DrvMsg(m->d.pI2CBus->scrnIndex,X_INFO,"Stereo Detection Register: %d\n",detect); #endif if (detect>=((m->c_mode==MSPMODE_STEREO)?stereo_off:stereo_on)) { m->c_mode=MSPMODE_STEREO; matrix=0x20; /*STEREO*/ fmmatrix=((m->c_standard==MSPSTANDARD_FM_M)?2:1); } else if (detect<=((m->c_mode==MSPMODE_AB)?dual_off:dual_on)) { m->c_mode=MSPMODE_AB; switch (m->mode) { case MSPMODE_STEREO_AB: matrix=0x20; break; case MSPMODE_STEREO_B: matrix=0x10; break; default: case MSPMODE_A: matrix=0x00; break; } } else { m->c_mode=MSPMODE_MONO; matrix=0x30; /*MONO*/ } break; } /* end - case mode*/ break; } if (m->c_fmmatrix != fmmatrix) { GetMSP3430Data (m, RD_DSP, 0x00, 0x0e, &high, &low); SetMSP3430Data (m, WR_DSP, 0x00, 0x0e, high, fmmatrix); m->c_fmmatrix = fmmatrix; } if ((m->c_matrix != matrix) || (m->c_source != source)) { /*set chanel source and matrix for loudspeaker*/ SetMSP3430Data (m, WR_DSP, 0x00, 0x08, source, matrix); m->c_matrix = matrix; m->c_source = source; } if ( ((m->c_format) & 0xF0) == MSPFORMAT_NICAM) SetMSP3430Data (m, WR_DEM, 0x00, 0x21, 0, 1); #if __MSPDEBUG__ > 0 char *msg; switch (matrix) { case 0x30: /*MONO*/ msg="MONO"; break; case 0x00: /*LEFT*/ msg="MONO/CHANNEL_1"; break; case 0x10: /*RIGHT*/ msg="MONO/CHANNEL_2"; break; case 0x20: /*LEFT*/ msg="STEREO"; break; default: msg="unknown"; break; } xf86DrvMsg(m->d.pI2CBus->scrnIndex,X_INFO,"Audio mode set to: %s\n",msg); #endif }