# -- coding: utf-8 --
from analyzer import Analyzer
from analyzer import register_analyzer
from analyzer import LogLevel
from .dmac    import DMAAnalyzer


class I2CAnalyzer(Analyzer):
    def run(self):
        super().run()
        self.log(LogLevel.INFO, f"{self.periph_name}分析启动")
        inst_num = self.get_instance_num(self.periph_name)
        inst_error = 0
        
        #前置检查开始，如果出错就需要中止分析，以防读寄存器卡死或出现非法值
        HPSYS_RCC = self.read_peripheral("hpsys_rcc")
        HPSYS_CFG = self.read_peripheral("hpsys_cfg")
        #self.print_reg(HPSYS_CFG)
        #以下是根据实例编号需要区分的内容
        if (inst_num==1):
            if (HPSYS_RCC.ENR1.I2C1!=1):
                self.log(LogLevel.ERROR, f"{self.periph_name}模块时钟未开启",\
                         "需将HPSYS_RCC的ESR1_I2C1置1以开启模块时钟")
                inst_error = 1
            if (HPSYS_RCC.RSTR1.I2C1!=0):
                self.log(LogLevel.ERROR, f"{self.periph_name}模块被复位",\
                         "需将HPSYS_RCC的RSTR1_I2C1置0以释放模块复位")
                inst_error = 1
            scl_pin = HPSYS_CFG.I2C1_PINR.SCL_PIN
            sda_pin = HPSYS_CFG.I2C1_PINR.SDA_PIN
            i2c_dma_idx = 22
        elif (inst_num==2):
            if (HPSYS_RCC.ENR1.I2C2!=1):
                self.log(LogLevel.ERROR, f"{self.periph_name}模块时钟未开启",\
                         "需将HPSYS_RCC的ESR1_I2C2置1以开启模块时钟")
                inst_error = 1
            if (HPSYS_RCC.RSTR1.I2C2!=0):
                self.log(LogLevel.ERROR, f"{self.periph_name}模块被复位",\
                         "需将HPSYS_RCC的RSTR1_I2C2置0以释放模块复位")
                inst_error = 1
            scl_pin = HPSYS_CFG.I2C2_PINR.SCL_PIN
            sda_pin = HPSYS_CFG.I2C2_PINR.SDA_PIN
            i2c_dma_idx = 23
        elif (inst_num==3):
            if (HPSYS_RCC.ENR2.I2C3!=1):
                self.log(LogLevel.ERROR, f"{self.periph_name}模块时钟未开启",\
                         "需将HPSYS_RCC的ESR2_I2C3置1以开启模块时钟")
                inst_error = 1
            if (HPSYS_RCC.RSTR2.I2C3!=0):
                self.log(LogLevel.ERROR, f"{self.periph_name}模块被复位",\
                         "需将HPSYS_RCC的RSTR2_I2C3置0以释放模块复位")
                inst_error = 1
            scl_pin = HPSYS_CFG.I2C3_PINR.SCL_PIN
            sda_pin = HPSYS_CFG.I2C3_PINR.SDA_PIN
            i2c_dma_idx = 24
        elif (inst_num==4):
            if (HPSYS_RCC.ENR2.I2C4!=1):
                self.log(LogLevel.ERROR, f"{self.periph_name}模块时钟未开启",\
                         "需将HPSYS_RCC的ESR2_I2C4置1以开启模块时钟")
                inst_error = 1
            if (HPSYS_RCC.RSTR2.I2C4!=0):
                self.log(LogLevel.ERROR, f"{self.periph_name}模块被复位",\
                         "需将HPSYS_RCC的RSTR2_I2C4置0以释放模块复位")
                inst_error = 1
            scl_pin = HPSYS_CFG.I2C4_PINR.SCL_PIN
            sda_pin = HPSYS_CFG.I2C4_PINR.SDA_PIN
            i2c_dma_idx = 3
            
        #以下是各实例共同的内容
        if (scl_pin==0x3f):
            inst_error = 1
            self.log(LogLevel.ERROR, "SCL没有分配IO",\
                     f"需将HPSYS_CFG->{self.periph_name.upper()}_PINR.SCL_PIN配置到对应IO")
        elif (scl_pin>44):
            inst_error = 1
            self.log(LogLevel.ERROR, f"SCL分配的PA{scl_pin:02d}不存在",\
                     "可分配IO为PA00~PA44")
        else:
            self.log(LogLevel.INFO, f"SCL分配到PA{scl_pin:02d}")
        if (sda_pin==0x3f):
            inst_error = 1
            self.log(LogLevel.ERROR, "SDA没有分配IO",\
                     f"需将HPSYS_CFG->{self.periph_name.upper()}_PINR.SDA_PIN配置到对应IO")
        elif (sda_pin>44):
            inst_error = 1
            self.log(LogLevel.ERROR, f"SDA分配的PA{sda_pin:02d}不存在",\
                     "可分配IO为PA00~PA44")
        else:
            self.log(LogLevel.INFO, f"SDA分配到PA{sda_pin:02d}")
        #TODO，check_pin_collision()

        #前置检查结束，如果出错就中止分析
        if (inst_error==1):
            self.log(LogLevel.ERROR, f"{self.periph_name}上述初始配置发现错误，分析无法继续进行",\
                     "请将上述错误修正后重新启动分析")
            return
        #self.log(LogLevel.INFO, "初始配置分析完成，未发现错误，启动功能分析")

        #PINMUX检查
        HPSYS_PINMUX = self.read_peripheral("hpsys_pinmux")
        #SCL IO检查
        if (scl_pin<=44):
            if (HPSYS_PINMUX.__dict__[f"PAD_PA{scl_pin:02d}"].FSEL != 4):
                self.log(LogLevel.ERROR, f"SCL PA{scl_pin:02d}功能错误，未选择I2C功能",\
                         f"应将HPSYS_PINMUX->PAD_PA{scl_pin:02d}.FSEL设为4")
                inst_error = 1
            if (HPSYS_PINMUX.__dict__[f"PAD_PA{scl_pin:02d}"].IE != 1):
                self.log(LogLevel.ERROR, f"SCL PA{scl_pin:02d}输入未使能",\
                         f"应将HPSYS_PINMUX->PAD_PA{scl_pin:02d}.IE设为1")
                inst_error = 1
            if ((HPSYS_PINMUX.__dict__[f"PAD_PA{scl_pin:02d}"].PE == 1) and \
                (HPSYS_PINMUX.__dict__[f"PAD_PA{scl_pin:02d}"].PS == 0)):
                self.log(LogLevel.ERROR, f"SCL PA{scl_pin:02d}内部下拉开启，会产生漏电",\
                         f"应将HPSYS_PINMUX->PAD_PA{scl_pin:02d}.PE设为0以关闭内部下拉，同时芯片外部应有上拉电阻")
                inst_error = 1
        #SDA IO检查
        if (sda_pin<=44):
            if (HPSYS_PINMUX.__dict__[f"PAD_PA{sda_pin:02d}"].FSEL != 4):
                self.log(LogLevel.ERROR, f"SCL PA{sda_pin:02d}功能错误，未选择I2C功能",\
                         f"应将HPSYS_PINMUX->PAD_PA{sda_pin:02d}.FSEL设为4")
                inst_error = 1
            if (HPSYS_PINMUX.__dict__[f"PAD_PA{sda_pin:02d}"].IE != 1):
                self.log(LogLevel.ERROR, f"SCL PA{sda_pin:02d}输入未使能",\
                         f"应将HPSYS_PINMUX->PAD_PA{sda_pin:02d}.IE设为1")
                inst_error = 1
            if ((HPSYS_PINMUX.__dict__[f"PAD_PA{sda_pin:02d}"].PE == 1) and \
                (HPSYS_PINMUX.__dict__[f"PAD_PA{sda_pin:02d}"].PS == 0)):
                self.log(LogLevel.ERROR, f"SCL PA{sda_pin:02d}内部下拉开启，会产生漏电",\
                         f"应将HPSYS_PINMUX->PAD_PA{sda_pin:02d}.PE设为0以关闭内部下拉，同时芯片外部应有上拉电阻")
                inst_error = 1
               
        #读取I2C寄存器
        I2C = self.read_peripheral(self.periph_name)
        
        #检查I2C速率
        i2c_mode = I2C.CR.MODE;
        if (i2c_mode == 0):
            self.log(LogLevel.INFO, "I2C为标准模式(standard-mode)")
            if (I2C.LCR.SLV > (2*I2C.WCR.CNT + 6)):
                i2c_freq = 48000/(2*I2C.LCR.SLV + 7 + I2C.CR.DNF);
            else:
                i2c_freq = 48000/(I2C.LCR.SLV + 2*I2C.WCR.CNT + 6 + 7 + I2C.CR.DNF);
        elif (i2c_mode == 1):
            self.log(LogLevel.INFO, "I2C为快速或快速增强模式(fast-mode/fast-mode plus)")
            if (I2C.LCR.FLV > (2*I2C.WCR.CNT + 6)):
                i2c_freq = 48000/(2*I2C.LCR.FLV + 7 + I2C.CR.DNF);
            else:
                i2c_freq = 48000/(I2C.LCR.FLV + 2*I2C.WCR.CNT + 6 + 7 + I2C.CR.DNF);
        else:
            self.log(LogLevel.WARN, "I2C为高速模式，需使用专用格式访问",\
                     "请确认是否需要3.4M高速模式(HS-mode)")
            i2c_freq = 48000/(I2C.LCR.HLVH + I2C.LCR.HLVL + 7 + 2*I2C.CR.DNF);
        self.log(LogLevel.INFO, f"接口频率约{i2c_freq:.0f}kHz")
        if (i2c_freq >= 1000):
            self.log(LogLevel.INFO, "外部上拉电阻推荐1K欧")
        elif (i2c_freq >= 400):
            self.log(LogLevel.INFO, "外部上拉电阻推荐4.7K欧")
        #检查I2C内部复位
        if (I2C.CR.UR == 1):
            self.log(LogLevel.WARN, "I2C处于复位状态",\
                     f"将I2C{inst_num}->CR.UR置0以解除复位")
        #检查I2C模式
        if (I2C.CR.SLVEN == 1):
            self.log(LogLevel.INFO, "I2C处于主从模式，既可做master也可做slave")
            self.log(LogLevel.INFO, f"作为slave时，7bit地址是0x{I2C.SAR.value:2X}")
        else:
            self.log(LogLevel.INFO, "I2C处于主模式，仅作为master，不能做slave")
        #检查SCL输出模式
        if (I2C.CR.SCLPP == 1):
            self.log(LogLevel.INFO, "SCL处于推挽模式(PUSH-PULL),不支持clock stretch")
        #检查SCL输出使能
        if (I2C.CR.SCLE == 0):
            self.log(LogLevel.WARN, "SCL输出关闭",f"将I2C{inst_num}->CR.SCLE置1使能SCL输出")
        #检查I2C工作状态
        if (I2C.CR.IUE == 0):
            self.log(LogLevel.WARN, "I2C未开始工作",f"将I2C{inst_num}->CR.IUE置1开始I2C工作")
        if (I2C.SR.SAD == 1):
            self.log(LogLevel.WARN, "I2C作为slave被寻址成功",\
                     f"如果{self.periph_name}只需作为master，应将I2C{inst_num}->CR.SLVEN置0")
        if (I2C.SR.UB == 1):
            self.log(LogLevel.WARN, "I2C传输未结束",\
                     f"如果传输长时间无法结束，怀疑I2C挂死，可将I2C{inst_num}->CR.BRGRST置1复位I2C")
        if (I2C.SR.NACK == 1):
            self.log(LogLevel.INFO, "最近一次传输以NACK结束")
        if (I2C.SR.UF == 1):
            self.log(LogLevel.WARN, "FIFO下溢出","请检查总线频率与DMAC状态")
        if (I2C.SR.OF == 1):
            self.log(LogLevel.WARN, "FIFO溢出","请检查总线频率与DMAC状态")
            
        #检查DMA
        if (I2C.CR.DMAEN == 1):
            self.log(LogLevel.INFO, "DMA模式已开启，数据接收与发送通过DMA进行，但设备寻址与寄存器寻址仍需CPU处理")
            self.log(LogLevel.INFO, f"还剩{I2C.DNR.NDT}个字节待传输")
            dmac = DMAAnalyzer(self.device,"dmac1",self.port,self.baudrate);
            i2c_dma_ch = dmac.check_dmac1_mapping(i2c_dma_idx)
            if (i2c_dma_ch == 0):
                self.log(LogLevel.ERROR, f"{self.periph_name}没有映射到DMAC1的任何通道上",\
                         f"请检查DMAC1对应通道的CSELR1/2_CxS配置，应配置为{i2c_dma_idx}")
                inst_error = 1
            else:
                self.log(LogLevel.INFO, f"{self.periph_name}映射到DMAC1的通道{i2c_dma_ch}上")
                
        #检查I2C总线状态
        if (I2C.BMR.SCL == 0):
            self.log(LogLevel.WARN, "SCL被拉低",\
                     f"如果当前未处于传输状态，可能是SCL上拉电阻失效，或者外设挂死,\
请尝试发送总线复位(I2C{inst_num}->CR.RSTREQ置1)")
        if (I2C.BMR.SDA == 0):
            self.log(LogLevel.WARN, "SDA被拉低",\
                     f"如果当前未处于传输状态，可能是SDA上拉电阻失效，或者外设挂死,\
请尝试发送总线复位(I2C{inst_num}->CR.RSTREQ置1)")
            
        #输出分析结果
        if (inst_error==1):
            self.log(LogLevel.ERROR, f"{self.periph_name}配置发现错误",\
                     "请检查配置")
        else:
            self.log(LogLevel.INFO, f"{self.periph_name}分析结束，未发现错误")
        
register_analyzer("I2C", I2CAnalyzer)

