/* -*- mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8 -*- */ /************************************************************************************ ** ** ** mcHF QRP Transceiver ** ** K Atanassov - M0NKA 2014 ** ** ** **---------------------------------------------------------------------------------** ** ** ** File name: ** ** Description: ** ** Last Modified: ** ** Licence: GNU GPLv3 ** ************************************************************************************/ // Common #include "uhsdr_board.h" #include "uhsdr_hw_i2c.h" #include "serial_eeprom.h" // for MAX_VAR_ADDR only #include "ui_configuration.h" typedef struct SerialEEPROM_Configuration { uint8_t device_type; uint8_t use_type; bool detection; } SerialEEPROM_Configuration_t; // static SerialEEPROM_Configuration_t ser_eeprom_config; const SerialEEPROM_EEPROMTypeDescriptor SerialEEPROM_eepromTypeDescs[SERIAL_EEPROM_DESC_NUM] = { // 0 { .size = 0, .supported = false, .pagesize = 0, .name = "No EEPROM" }, // 1 { .size = 0, .supported = false, .pagesize = 0, .name = "Wrong Signature" }, // 2 { .size = 0, .supported = false, .pagesize = 0, .name = "Unknown Type" }, // 3 { .size = 0, .supported = false, .pagesize = 0, .name = "Not used" }, // 4 { .size = 0, .supported = false, .pagesize = 64, .name = "Not used" }, // 5 { .size = 0, .supported = false, .pagesize = 0, .name = "Not used" }, // 6 { .size = 0, .supported = false, .pagesize = 0, .name = "Not used" }, // 7 { .size = 128, .supported = false, .pagesize = 8, .name = "24xx01" }, // 8 { .size = 256, .supported = false, .pagesize = 8, .name = "24xx02" }, // 9 { .size = 2*256, .supported = false, .pagesize = 16, .name = "24xx04" }, // 10 { .size = 1*1024, .supported = false, .pagesize = 16, .name = "24xx08" }, // 11 { .size = 2*1024, .supported = false, .pagesize = 16, .name = "24xx16" }, // 12 { .size = 4*1024, .supported = false, .pagesize = 32, .name = "24xx32" }, // 13 { .size = 8*1024, .supported = false, .pagesize = 32, .name = "24xx64" }, // 14 { .size = 16*1024, .supported = false, .pagesize = 64, .name = "24xx128" }, // 15 { .size = 32*1024, .supported = true, .pagesize = 64, .name = "24xx256" }, // 16 { .size = 64*1024, .supported = true, .pagesize = 128, .name = "24xx512" }, // 17 { .size = 128*1024, .supported = true, .pagesize = 128, .name = "24xx1025" }, // 18 { .size = 128 * 1024, .supported = true, .pagesize = 128, .name = "24xx1026/CM01" }, // 19 { .size = 256*1024, .supported = true, .pagesize = 256, .name = "24CM02" } }; typedef struct { uint8_t devaddr; uint16_t addr; uint16_t addr_size; } SerialEEPROM_24CXX_Descriptor; static SerialEEPROM_24CXX_Descriptor serialEeprom_desc; // THIS CAN BE USED ONLY WITH SINGLE EEPROM AND SINGLE THREAD // NOT THREAD SAFE, USE local variable instead then #define MEM_DEVICE_WRITE_ADDR 0xA0 // serial eeprom functions by DF8OE static uint16_t SerialEEPROM_24Cxx_DeviceConnected(void) { uint16_t retVal = UhsdrHw_I2C_DeviceReady(&hi2c2,MEM_DEVICE_WRITE_ADDR); return retVal; } static void SerialEEPROM_24Cxx_AdjustAddrs(const uint8_t Mem_Type, uint8_t* devaddr_ptr, uint32_t* Addr_ptr) { *devaddr_ptr = MEM_DEVICE_WRITE_ADDR; if (*Addr_ptr > 0xFFFF) { switch (Mem_Type) { case 17: // 24LC1025 *devaddr_ptr = MEM_DEVICE_WRITE_ADDR + 8; // 24LC1025 break; case 18: *devaddr_ptr = MEM_DEVICE_WRITE_ADDR + 2; // 24LC1026 break; case 19: // 24CM02 *devaddr_ptr = MEM_DEVICE_WRITE_ADDR + ((*Addr_ptr & 0x30000) >> 15); // the upper bits 16 and 17 determine the I2C address offset break; } *Addr_ptr &= 0xFFFF; // mask address to 16bits in all cases } } static uint16_t SerialEEPROM_24Cxx_ackPolling(uint32_t Addr, uint8_t Mem_Type) { uint8_t devaddr; SerialEEPROM_24Cxx_AdjustAddrs(Mem_Type,&devaddr,&Addr); uint16_t retVal = UhsdrHw_I2C_DeviceReady(&hi2c2,devaddr); // != HAL_OK?0xFD00:0; return retVal; } static void SerialEEPROM_24Cxx_StartTransfer_Prep(uint32_t Addr, uint8_t Mem_Type, SerialEEPROM_24CXX_Descriptor* eeprom_desc_ptr) { SerialEEPROM_24Cxx_AdjustAddrs(Mem_Type,&eeprom_desc_ptr->devaddr,&Addr); eeprom_desc_ptr->addr = (uint16_t)((0xFFFF)&(Addr)); if (Mem_Type > 8) { eeprom_desc_ptr->addr_size = I2C_MEMADD_SIZE_16BIT; } else { eeprom_desc_ptr->addr_size = I2C_MEMADD_SIZE_8BIT; } } uint16_t SerialEEPROM_24Cxx_Write(uint32_t Addr, uint8_t Data, uint8_t Mem_Type) { SerialEEPROM_24Cxx_StartTransfer_Prep(Addr, Mem_Type,&serialEeprom_desc); uint16_t retVal = UhsdrHw_I2C_WriteRegister(SERIALEEPROM_I2C,serialEeprom_desc.devaddr,serialEeprom_desc.addr,serialEeprom_desc.addr_size,Data); if (!retVal) { retVal = SerialEEPROM_24Cxx_ackPolling(Addr,Mem_Type); } return retVal; } uint16_t SerialEEPROM_24Cxx_Read(uint32_t Addr, uint8_t Mem_Type) { uint8_t value; SerialEEPROM_24Cxx_StartTransfer_Prep(Addr, Mem_Type,&serialEeprom_desc); uint16_t retVal = UhsdrHw_I2C_ReadRegister(SERIALEEPROM_I2C,serialEeprom_desc.devaddr,serialEeprom_desc.addr,serialEeprom_desc.addr_size,&value); if (!retVal) { retVal = value; } return retVal; } uint16_t SerialEEPROM_24Cxx_ReadBulk(uint32_t Addr, uint8_t *buffer, uint16_t length, uint8_t Mem_Type) { uint16_t retVal = 0xFFFF; if (Mem_Type < SERIAL_EEPROM_DESC_NUM) { uint32_t page, count; count = 0; page =SerialEEPROM_eepromTypeDescs[Mem_Type].pagesize; while(count < length) { if (length - count < page) { // make sure we do not read more than asked for! Buffer Overflow! page = (length - count); } SerialEEPROM_24Cxx_StartTransfer_Prep(Addr + count, Mem_Type,&serialEeprom_desc); retVal = UhsdrHw_I2C_ReadBlock(SERIALEEPROM_I2C,serialEeprom_desc.devaddr,serialEeprom_desc.addr,serialEeprom_desc.addr_size,&buffer[count],page); count+=page; if (retVal) { break; } } } return retVal; } /** * Write a consecutive area of memory into the I2C eeprom starting with Addr. * @param Addr the location of the first byte in the eeprom. May have any value within the memory size of the eeprom * @param buffer memory to copy from * @param length how many bytes to copy * @param Mem_Type which eeprom type are we having * @return 0 if success, I2C error code otherwise */ uint16_t SerialEEPROM_24Cxx_WriteBulk(uint32_t Addr, const uint8_t *buffer, uint16_t length, uint8_t Mem_Type) { uint16_t retVal = 0; if (Mem_Type < SERIAL_EEPROM_DESC_NUM) { uint32_t count = 0; const uint32_t page = SerialEEPROM_eepromTypeDescs[Mem_Type].pagesize; while(retVal == 0 && count < length) { SerialEEPROM_24Cxx_StartTransfer_Prep(Addr + count, Mem_Type,&serialEeprom_desc); uint32_t transfer_size = page; // correct the transfer_size to keep inside a single page // if the start address is not on a page boundary // this will happen only for the first bulkd write (if at all) // then the Addr + count will be aligned on page boundaries transfer_size -= ((Addr+count) % page); if (length - count < transfer_size) { transfer_size = length - count; } retVal = UhsdrHw_I2C_WriteBlock(SERIALEEPROM_I2C,serialEeprom_desc.devaddr,serialEeprom_desc.addr,serialEeprom_desc.addr_size,&buffer[count],transfer_size); count+=transfer_size; if (retVal) { break; } retVal = SerialEEPROM_24Cxx_ackPolling(Addr,Mem_Type); } } return retVal; } /** * @brief in general a non-destructive probing to identify the used EEPROM, in some cases devices are written (and changed data restored) * this code uses hardware properties to identify the connected I2C eeprom, no previously signature etc. used or required * @returns identified eeprom chip type or EEPROM_SER_UNKNOWN */ static uint8_t SerialEEPROM_24Cxx_DetectProbeHardware(void) { uint8_t ser_eeprom_type = EEPROM_SER_UNKNOWN; const uint8_t testsignatureA = 0x66; const uint8_t testsignatureB = 0x77; // has to be different from testsignature 1 const uint16_t testaddr1 = 0x0001; const uint16_t testaddr1plus128 = testaddr1 + 0x80; // 8 Bit Test First uint16_t test8Byte1 = SerialEEPROM_24Cxx_Read(testaddr1,8); // first decide if 8 or 16 bit addressable eeprom by trying to read with 8 or 16 bit algorithm // if an algorithm succeeds we know the address width if(test8Byte1 < 0x100) { // 8 bit addressing uint16_t test8Byte2 = SerialEEPROM_24Cxx_Read(testaddr1plus128,8); SerialEEPROM_24Cxx_Write(testaddr1,testsignatureB,8); SerialEEPROM_24Cxx_Write(testaddr1plus128,testsignatureB,8); if (SerialEEPROM_24Cxx_Read(testaddr1,8) == testsignatureB) { SerialEEPROM_24Cxx_Write(testaddr1,testsignatureA,8); // write test signature ser_eeprom_type = 7; // smallest possible 8 bit EEPROM (128Bytes) if(SerialEEPROM_24Cxx_Read(testaddr1plus128,8) == testsignatureB) { ser_eeprom_type = 8; } } SerialEEPROM_24Cxx_Write(testaddr1,test8Byte1,8); // write back old data SerialEEPROM_24Cxx_Write(testaddr1plus128,test8Byte2,8); } if (ser_eeprom_type == EEPROM_SER_UNKNOWN) { // 16 bit addressing check // the other banks are mapped to different I2C // device addresses. We simply try to read from them // and if it succeeds we know the device type // We need to check unique addresses for each EEPROM type // 0xA0 + 0x10000: 0xA8 if(SerialEEPROM_24Cxx_Read(0x10000,17) < 0x100) { ser_eeprom_type = 17; // 24LC1025 } // 0xA0 + 0x10000: 0xA2 if(SerialEEPROM_24Cxx_Read(0x10000,18) < 0x100) { ser_eeprom_type = 18; // 24LC1026 } // 0xA0 + 0x10000: 0xA2 + 0x20000: 0xA4 + 0x30000: 0xA6 if(SerialEEPROM_24Cxx_Read(0x20000,19) < 0x100) { ser_eeprom_type = 19; // 24CM02 } // it is not a large EEPROM (i.e. >64KB Data) // // the following test requires writing to EEPROM, // we remember content of test locations // and write them back later if(ser_eeprom_type == EEPROM_SER_UNKNOWN) { const uint16_t testaddr1plus256 = 0x0100 + testaddr1; // same as before but 0x100 == 256 byte spacing uint16_t testByte1 = SerialEEPROM_24Cxx_Read(testaddr1,16); // read old content uint16_t testByte2 = SerialEEPROM_24Cxx_Read(testaddr1plus256,16); // of test bytes if (testByte1 < 0x100 && testByte2 < 0x100) { // successful read from both locations, let us start SerialEEPROM_24Cxx_Write(testaddr1, testsignatureA,16); // write testsignature 1 SerialEEPROM_24Cxx_Write(testaddr1plus256, testsignatureB,16); // write testsignature 2 if(SerialEEPROM_24Cxx_Read(testaddr1,16) == testsignatureA && SerialEEPROM_24Cxx_Read(testaddr1plus256,16) == testsignatureB) { // 16 bit addressing ser_eeprom_type = 9; // smallest possible 16 bit EEPROM // we look for the "looping" in data, i.e. we read back 0x66 // which we wrote to address 0x0003 // from a address we did not wrote it to // once it occurs, we know the size of the EEPROM // since all of these EEPROMS are ignoring the unused bits in the address for (int shift = 0; shift < 8; shift++) { uint16_t shift_test_addr = (0x200 << shift)+testaddr1; if(SerialEEPROM_24Cxx_Read(shift_test_addr,16) == testsignatureA) { // now we write test signature 2 to make sure the match was no accident // i.e. match with same content in EEPROM SerialEEPROM_24Cxx_Write(shift_test_addr,testsignatureB,16); if(SerialEEPROM_24Cxx_Read(testaddr1,16) == testsignatureB) { // we found the looping, // now stop checking and set EEPROM type ser_eeprom_type = 9 + shift; break; } else { // oops: just an accident, so we write back old content and continue SerialEEPROM_24Cxx_Write(shift_test_addr,testsignatureA,16); } } } } SerialEEPROM_24Cxx_Write(testaddr1,testByte1,16); // write back old data SerialEEPROM_24Cxx_Write(testaddr1plus256,testByte2,16); } } } return ser_eeprom_type; } static void SerialEEPROM_Read_Signature(uint8_t ser_eeprom_type, uint16_t* type_p, uint16_t* state_p) { *type_p = SerialEEPROM_24Cxx_Read(0,ser_eeprom_type); *state_p = SerialEEPROM_24Cxx_Read(1,ser_eeprom_type); } uint16_t SerialEEPROM_Set_UseStateInSignature(uint8_t state) { return SerialEEPROM_24Cxx_Write(1,SER_EEPROM_IN_USE,ts.ser_eeprom_type); } uint16_t SerialEEPROM_Get_UseStateInSignature() { return SerialEEPROM_24Cxx_Read(1,ts.ser_eeprom_type); } uint8_t SerialEEPROM_Detect() { uint16_t ser_eeprom_type = EEPROM_SER_UNKNOWN; // serial EEPROM init if(SerialEEPROM_24Cxx_DeviceConnected() != HAL_OK || SerialEEPROM_24xx_Exists() == false) // Issue with Ser EEPROM, either not available or other problems { ser_eeprom_type = EEPROM_SER_NONE; // no serial EEPROM available } else { // this initial read will bring us the first byte of any eeprom (8 or 16bits) // if we read anything but the 0xff == SER_EERPM_NOT_IN_USE value which indicates empty eeprom // we assume some configuration information tells us what EEPROM we have and how it is used if(SerialEEPROM_24Cxx_Read(0,16) != SER_EEPROM_NOT_IN_USE) { ser_eeprom_type = EEPROM_SER_WRONG_SIG; // unless we find a correct signature, we have to assume the EEPROM has incorrect/corrupted data uint16_t ser_eeprom_type_read; uint16_t ser_eeprom_sig; SerialEEPROM_Read_Signature(8, &ser_eeprom_type_read, &ser_eeprom_sig); // all 8 bit (i.e. 256 or 128 Byte) EEPROMS are marked as "too small" during detection if(ser_eeprom_sig == SER_EEPROM_TOO_SMALL && ser_eeprom_type_read >= SERIAL_EEPROM_DESC_REAL && ser_eeprom_type_read < 9) { ser_eeprom_type = ser_eeprom_type_read; } else { SerialEEPROM_Read_Signature(16, &ser_eeprom_type_read, &ser_eeprom_sig); // we either have a new EEPROM, just being initialized ( ser_eeprom_sig = SER_EEPROM_NOT_IN_USE && valid type) or // we have an used EEPROM (ser_eeprom_sig = SER_EEPROM_IN_USE && valid type) // please note, that even though no 16 bit EEPROM gets the "too small" signature written in, these may still be // consider too small by the later code. Only EEPROMS which have the "supported" flag set in the descriptor will // be consider of sufficient size for actual use if((ser_eeprom_sig == SER_EEPROM_IN_USE || ser_eeprom_sig == SER_EEPROM_NOT_IN_USE) && ser_eeprom_type_read < SERIAL_EEPROM_DESC_NUM && ser_eeprom_type_read > 8) { ser_eeprom_type = ser_eeprom_type_read; } } } else { ser_eeprom_type = SerialEEPROM_24Cxx_DetectProbeHardware(); if (ser_eeprom_type >= SERIAL_EEPROM_DESC_REAL && ser_eeprom_type < SERIAL_EEPROM_DESC_NUM) { SerialEEPROM_24Cxx_Write(0,ser_eeprom_type,ser_eeprom_type); if (SerialEEPROM_eepromTypeDescs[ser_eeprom_type].supported == false) { SerialEEPROM_24Cxx_Write(1,SER_EEPROM_TOO_SMALL,ser_eeprom_type); } } } // just to be save. Never ever deliver a type id outside the array boundaries. if (ser_eeprom_type >= SERIAL_EEPROM_DESC_NUM) { ser_eeprom_type = EEPROM_SER_UNKNOWN; } } return ser_eeprom_type; } static void SerialEEPROM_Clear_Variable(uint16_t addr) { const uint8_t empty_var[2] = { 0xff, 0xff }; SerialEEPROM_24Cxx_WriteBulk(addr*2, empty_var,2, ts.ser_eeprom_type); } void SerialEEPROM_Clear_AllVariables() { // variable 0 is the reserved signature variable for(uint16_t count=1; count <= MAX_VAR_ADDR; count++) { SerialEEPROM_Clear_Variable(count); } } void SerialEEPROM_Clear_Signature() { // variable 0 is the reserved signature variable ts.ser_eeprom_type = SerialEEPROM_24Cxx_DetectProbeHardware(); SerialEEPROM_Clear_Variable(0); } bool SerialEEPROM_24xx_Exists() { return SerialEEPROM_24Cxx_Read(0,8) < 0x100; } // // Interface for serial EEPROM functions // uint16_t SerialEEPROM_ReadVariable(uint16_t addr, uint16_t *value) // reference to serial EEPROM read function { uint8_t bytes[2]; uint16_t retval = SerialEEPROM_24Cxx_ReadBulk(addr*2,bytes, 2, ts.ser_eeprom_type); if (retval == HAL_OK) { *value = ((uint16_t)bytes[0])<<8; *value |= bytes[1]; } return retval; } uint16_t SerialEEPROM_UpdateVariable(uint16_t addr, uint16_t value) { uint16_t value_read = 0; uint16_t retval = SerialEEPROM_ReadVariable(addr,&value_read); if (retval != 0 || value_read != value ) { retval = SerialEEPROM_WriteVariable(addr,value); } return retval; } uint16_t SerialEEPROM_WriteVariable(uint16_t addr, uint16_t value) // reference to serial EEPROM write function, writing unsigned 16 bit { uint8_t bytes[2]; bytes[0] = (uint8_t)((value&(0xFF00))>>8); bytes[1] = (uint8_t)(value&(0x00FF)); return SerialEEPROM_24Cxx_WriteBulk(addr*2, bytes, 2, ts.ser_eeprom_type); }