/** * @file sdrv_pvt.c * @brief SemiDrive Temperature sensor driver source file. * * @copyright Copyright (c) 2022 Semidrive Semiconductor. * All rights reserved. */ #include #include #include #include #include "sdrv_pvt.h" /* Length of PVT DOUT and Threshold register bits. */ #define PVT_THRESH_BITS (12) /* PVT registers */ #define PVT_CORE_REG_OFFSET (0x34) #define PVT_CORE_REGISTER(core, reg) ((core)*PVT_CORE_REG_OFFSET + (reg)) #define PVT_CTRL (0x0) #define PVT_CTRL_PDTSEN 1 #define PVT_CTRL_PDPSEN 2 #define PVT_CTRL_PTSEL 4 #define PVT_CTRL_LPMODE 7 #define PVT_CTRL_HL_MODE_EN(core) (8 + (core)*2) #define PVT_CTRL_RF_MODE_EN(core) (9 + (core)*2) #define PVT_DOUT (0x4) #define PVT_DOUT_VALID 0 #define PVT_DOUT_DATA 1 #define PVT_HYST_H(core) PVT_CORE_REGISTER(core, 0x8) #define PVT_HYST_H_THRESH_H 0 #define PVT_HYST_H_THRESH_L 12 #define PVT_HYST_L(core) PVT_CORE_REGISTER(core, 0xc) #define PVT_HYST_L_THRESH_H 0 #define PVT_HYST_L_THRESH_L 12 #define PVT_HYST_R(core) PVT_CORE_REGISTER(core, 0x10) #define PVT_HYST_R_ALARM 0 #define PVT_HYST_R_HYST 12 #define PVT_HYST_F(core) PVT_CORE_REGISTER(core, 0x14) #define PVT_HYST_F_ALARM 0 #define PVT_HYST_F_HYST 12 #define PVT_INT_EN(core) PVT_CORE_REGISTER(core, 0x30) #define PVT_INT_STATUS(core) PVT_CORE_REGISTER(core, 0x34) #define PVT_INT_CLR(core) PVT_CORE_REGISTER(core, 0x38) #define PVT_ALARM_INT(alarm) (uint32_t)(alarm) static inline sdrv_pvt_core_t *pvt_core(sdrv_pvt_t *dev, sdrv_pvt_core_id_e core) { return &dev->core[core]; } /* Convert DOUT register value to Celsius. */ static inline sdrv_pvt_type dout_to_celsius(uint32_t dout) { dout &= BIT_MASK(PVT_THRESH_BITS); #if CONFIG_PVT_TYPE_INT return (30676 * dout / 4096 - 7598 + 50) / 100; #else return 306.76f * dout / 4096 - 75.98f; #endif } /* Convert Celsius to DOUT register value. */ static inline uint32_t celsius_to_dout(sdrv_pvt_type celsius) { uint32_t dout = (uint32_t)((celsius * 100 + 7598) * 4096 / 30676); return dout & BIT_MASK(PVT_THRESH_BITS); } static int pvt_core_irq_handler(sdrv_pvt_t *dev, sdrv_pvt_core_id_e core) { sdrv_pvt_core_t *_core = pvt_core(dev, core); paddr_t base = dev->base; sdrv_pvt_alarm_e alarm; uint32_t en, status; en = readl(base + PVT_INT_EN(core)); status = readl(base + PVT_INT_STATUS(core)); /* Handle alarm IRQ, if it's enabled. */ for (alarm = PVT_HIGH_LEVEL_ALARM; alarm < PVT_ALARM_NR; alarm++) { if (BIT_SET(en, PVT_ALARM_INT(alarm)) && BIT_SET(status, PVT_ALARM_INT(alarm))) { if (_core->alarm_cb != NULL) { /* Call user alarm callback. */ _core->alarm_cb(dev, core, alarm, _core->cb_arg); } /* * Try to clear interrupt status. Level interrupt status are * cleared when INT_CLR bits are set, and DOUT has returned to * normal range. Edge interrupt status are cleared when INT_CLR * bits are set. * * Note that INT_CLR itself is not auto cleared, so we have to * clear INT_CLR manually. */ RMWREG32(base + PVT_INT_CLR(core), PVT_ALARM_INT(alarm), 1, 1); RMWREG32(base + PVT_INT_CLR(core), PVT_ALARM_INT(alarm), 1, 0); } } return 0; } static int pvt_irq_handler(uint32_t irq, void *arg) { sdrv_pvt_t *dev = arg; sdrv_pvt_core_id_e core; for (core = PVT_CORE0; core < PVT_CORE_NR; core++) { sdrv_pvt_core_t *_core = pvt_core(dev, core); if (irq == _core->irq) { pvt_core_irq_handler(dev, core); break; } } return 0; } static status_t update_alarm_irq(sdrv_pvt_t *dev, sdrv_pvt_core_id_e core) { sdrv_pvt_core_t *_core = pvt_core(dev, core); paddr_t base = dev->base; /* Update High/Low mode bit in PVT_CTRL register, which controls * Temperature and Process High/Low modes. */ if (BIT_SET(_core->alarm_enable_mask, PVT_HIGH_LEVEL_ALARM) || BIT_SET(_core->alarm_enable_mask, PVT_LOW_LEVEL_ALARM)) { RMWREG32(base + PVT_CTRL, PVT_CTRL_HL_MODE_EN(core), 1, 1); } else { RMWREG32(base + PVT_CTRL, PVT_CTRL_HL_MODE_EN(core), 1, 0); } #if SDRV_PVT_USE_EDGE_ALARM /* Update Rising/Falling mode bit in PVT_CTRL register, which controls * Temperature and Process Rising/Falling modes. */ if (BIT_SET(_core->alarm_enable_mask, PVT_RISING_EDGE_ALARM) || BIT_SET(_core->alarm_enable_mask, PVT_FALLING_EDGE_ALARM)) { RMWREG32(base + PVT_CTRL, PVT_CTRL_RF_MODE_EN(core), 1, 1); } else { RMWREG32(base + PVT_CTRL, PVT_CTRL_RF_MODE_EN(core), 1, 0); } #endif /* Enable alarm interrupt if necessary. */ if (_core->alarm_cb != NULL && _core->alarm_enable_mask != 0) { sdrv_pvt_alarm_e alarm; for (alarm = PVT_HIGH_LEVEL_ALARM; alarm < PVT_ALARM_NR; alarm++) { if (BIT_SET(_core->alarm_enable_mask, alarm)) { RMWREG32(base + PVT_INT_EN(core), PVT_ALARM_INT(alarm), 1, 1); } else { RMWREG32(base + PVT_INT_EN(core), PVT_ALARM_INT(alarm), 1, 0); } } /* Enable PVT interrupt. */ irq_enable(_core->irq); } else { /* All alarm disabled. Disable PVT interrupt. */ irq_disable(_core->irq); } return SDRV_STATUS_OK; } static status_t update_alarm_mode(sdrv_pvt_t *dev, sdrv_pvt_core_id_e core, sdrv_pvt_alarm_e alarm, bool enable) { sdrv_pvt_core_t *_core = pvt_core(dev, core); if (enable) { _core->alarm_enable_mask |= (1ul << alarm); } else { _core->alarm_enable_mask &= ~(1ul << alarm); } return update_alarm_irq(dev, core); } static bool is_valid_level_alarm_config(sdrv_pvt_alarm_e alarm, const sdrv_pvt_level_alarm_config_t *config) { (void)alarm; return config != NULL && config->thresh_low >= PVT_TEMPERATURE_MIN && config->thresh_high <= PVT_TEMPERATURE_MAX && celsius_to_dout(config->thresh_low) <= celsius_to_dout(config->thresh_high); } #if SDRV_PVT_USE_EDGE_ALARM static bool is_valid_edge_alarm_config(sdrv_pvt_alarm_e alarm, const sdrv_pvt_edge_alarm_config_t *config) { if (alarm == PVT_RISING_EDGE_ALARM) { return config != NULL && config->hyst >= PVT_TEMPERATURE_MIN && config->alarm <= PVT_TEMPERATURE_MAX && celsius_to_dout(config->hyst) < celsius_to_dout(config->alarm); } else { /* PVT_FALLING_EDGE_ALARM */ return config != NULL && config->alarm >= PVT_TEMPERATURE_MIN && config->hyst <= PVT_TEMPERATURE_MAX && celsius_to_dout(config->alarm) < celsius_to_dout(config->hyst); } } #endif static status_t enable_level_alarm(sdrv_pvt_t *dev, sdrv_pvt_core_id_e core, sdrv_pvt_alarm_e alarm, const sdrv_pvt_level_alarm_config_t *config, bool enable) { paddr_t base = dev->base; if (enable) { if (!is_valid_level_alarm_config(alarm, config)) { ssdk_printf(SSDK_ERR, "Invalid level alarm config!\r\n"); return SDRV_PVT_INVALID_PARAM; } /* Update High/Low alarm thresholds. */ if (alarm == PVT_HIGH_LEVEL_ALARM) { RMWREG32(base + PVT_HYST_H(core), PVT_HYST_H_THRESH_H, PVT_THRESH_BITS, celsius_to_dout(config->thresh_high)); RMWREG32(base + PVT_HYST_H(core), PVT_HYST_H_THRESH_L, PVT_THRESH_BITS, celsius_to_dout(config->thresh_low)); } else { /* PVT_LOW_LEVEL_ALARM */ RMWREG32(base + PVT_HYST_L(core), PVT_HYST_L_THRESH_H, PVT_THRESH_BITS, celsius_to_dout(config->thresh_high)); RMWREG32(base + PVT_HYST_L(core), PVT_HYST_L_THRESH_L, PVT_THRESH_BITS, celsius_to_dout(config->thresh_low)); } } return update_alarm_mode(dev, core, alarm, enable); } #if SDRV_PVT_USE_EDGE_ALARM static status_t enable_edge_alarm(sdrv_pvt_t *dev, sdrv_pvt_core_id_e core, sdrv_pvt_alarm_e alarm, const sdrv_pvt_edge_alarm_config_t *config, bool enable) { paddr_t base = dev->base; if (enable) { if (!is_valid_edge_alarm_config(alarm, config)) { ssdk_printf(SSDK_ERR, "Invalid edge alarm config!\r\n"); return SDRV_PVT_INVALID_PARAM; } /* Update Rising/Falling alarm thresholds. */ if (alarm == PVT_RISING_EDGE_ALARM) { RMWREG32(base + PVT_HYST_R(core), PVT_HYST_R_ALARM, PVT_THRESH_BITS, celsius_to_dout(config->alarm)); RMWREG32(base + PVT_HYST_R(core), PVT_HYST_R_HYST, PVT_THRESH_BITS, celsius_to_dout(config->hyst)); } else { /* PVT_FALLING_EDGE_ALARM */ RMWREG32(base + PVT_HYST_F(core), PVT_HYST_F_ALARM, PVT_THRESH_BITS, celsius_to_dout(config->alarm)); RMWREG32(base + PVT_HYST_F(core), PVT_HYST_F_HYST, PVT_THRESH_BITS, celsius_to_dout(config->hyst)); } } return update_alarm_mode(dev, core, alarm, enable); } #endif static bool is_temperature_sensor_running(sdrv_pvt_t *dev) { uint32_t ctrl = readl(dev->base + PVT_CTRL); return !BIT_SET(ctrl, PVT_CTRL_PDTSEN) && !BIT_SET(ctrl, PVT_CTRL_PTSEL); } status_t sdrv_pvt_init(sdrv_pvt_t *dev) { ASSERT(dev != NULL); paddr_t base = dev->base; sdrv_pvt_core_id_e core; for (core = PVT_CORE0; core < PVT_CORE_NR; core++) { sdrv_pvt_core_t *_core = pvt_core(dev, core); /* Disable all interrupts by default. */ writel(0ul, base + PVT_INT_EN(core)); _core->alarm_cb = NULL; _core->cb_arg = NULL; _core->alarm_enable_mask = 0ul; irq_attach(_core->irq, pvt_irq_handler, dev); irq_disable(_core->irq); } return SDRV_STATUS_OK; } status_t sdrv_pvt_deinit(sdrv_pvt_t *dev) { ASSERT(dev != NULL); paddr_t base = dev->base; sdrv_pvt_core_id_e core; for (core = PVT_CORE0; core < PVT_CORE_NR; core++) { sdrv_pvt_core_t *_core = pvt_core(dev, core); irq_disable(_core->irq); irq_detach(_core->irq); /* Disable all interrupts. */ writel(0, base + PVT_INT_EN(core)); _core->alarm_cb = NULL; _core->cb_arg = NULL; _core->alarm_enable_mask = 0ul; } return SDRV_STATUS_OK; } status_t sdrv_pvt_start(sdrv_pvt_t *dev) { ASSERT(dev != NULL); paddr_t base = dev->base; /* Low power mode disable */ RMWREG32(base + PVT_CTRL, PVT_CTRL_LPMODE, 1, 0); /* Disable T sensor */ RMWREG32(base + PVT_CTRL, PVT_CTRL_PDTSEN, 1, 1); /* Select temperature sensor output. */ RMWREG32(base + PVT_CTRL, PVT_CTRL_PTSEL, 1, 0); /* Enable T sensor. */ RMWREG32(base + PVT_CTRL, PVT_CTRL_PDTSEN, 1, 0); return SDRV_STATUS_OK; } status_t sdrv_pvt_stop(sdrv_pvt_t *dev) { ASSERT(dev != NULL); /* Disable T sensor */ RMWREG32(dev->base + PVT_CTRL, PVT_CTRL_PDTSEN, 1, 1); return SDRV_STATUS_OK; } status_t sdrv_pvt_power_down(sdrv_pvt_t *dev) { ASSERT(dev != NULL); paddr_t base = dev->base; /* Low power mode enable */ RMWREG32(base + PVT_CTRL, PVT_CTRL_LPMODE, 1, 1); /* Power down T-sensor, P-sensor */ RMWREG32(base + PVT_CTRL, PVT_CTRL_PDTSEN, 2, 3); RMWREG32(base + PVT_CTRL, PVT_CTRL_PDTSEN, 2, 0); RMWREG32(base + PVT_CTRL, PVT_CTRL_PDTSEN, 2, 3); return SDRV_STATUS_OK; } status_t sdrv_pvt_get(sdrv_pvt_t *dev, sdrv_pvt_type *temperature) { uint32_t dout; ASSERT(dev != NULL && temperature != NULL); if (!is_temperature_sensor_running(dev)) { ssdk_printf(SSDK_ERR, "Temperature sensor is not started!\r\n"); return SDRV_PVT_NOT_STARTED; } /* * Read temperature data. PVT_DOUT is automatically updated every 280ms * after sdrv_pvt_start() is called. */ dout = (readl(dev->base + PVT_DOUT) >> PVT_DOUT_DATA) & BIT_MASK(PVT_THRESH_BITS); /* Convert data to Celsius. */ *temperature = dout_to_celsius(dout); return SDRV_STATUS_OK; } status_t sdrv_pvt_register_alarm_cb(sdrv_pvt_t *dev, sdrv_pvt_core_id_e core, sdrv_pvt_alarm_cb_t alarm_cb, void *arg) { ASSERT(dev != NULL && core < PVT_CORE_NR); sdrv_pvt_core_t *_core = pvt_core(dev, core); _core->alarm_cb = alarm_cb; _core->cb_arg = arg; return update_alarm_irq(dev, core); } status_t sdrv_pvt_enable_alarm(sdrv_pvt_t *dev, sdrv_pvt_core_id_e core, sdrv_pvt_alarm_e alarm, const sdrv_pvt_alarm_config_t *config) { ASSERT(dev != NULL && core < PVT_CORE_NR && config != NULL); status_t ret = SDRV_PVT_INVALID_PARAM; if (!is_temperature_sensor_running(dev)) { ssdk_printf(SSDK_ERR, "Temperature sensor is not started!\r\n"); return SDRV_PVT_NOT_STARTED; } switch (alarm) { case PVT_HIGH_LEVEL_ALARM: case PVT_LOW_LEVEL_ALARM: ret = enable_level_alarm(dev, core, alarm, &config->level_alarm, true); break; #if SDRV_PVT_USE_EDGE_ALARM case PVT_RISING_EDGE_ALARM: case PVT_FALLING_EDGE_ALARM: ret = enable_edge_alarm(dev, core, alarm, &config->edge_alarm, true); break; #endif default: break; } return ret; } status_t sdrv_pvt_disable_alarm(sdrv_pvt_t *dev, sdrv_pvt_core_id_e core, sdrv_pvt_alarm_e alarm) { ASSERT(dev != NULL && core < PVT_CORE_NR); status_t ret = SDRV_PVT_INVALID_PARAM; switch (alarm) { case PVT_HIGH_LEVEL_ALARM: case PVT_LOW_LEVEL_ALARM: ret = enable_level_alarm(dev, core, alarm, NULL, false); break; #if SDRV_PVT_USE_EDGE_ALARM case PVT_RISING_EDGE_ALARM: case PVT_FALLING_EDGE_ALARM: ret = enable_edge_alarm(dev, core, alarm, NULL, false); break; #endif default: break; } return ret; }