@@ -300,6 +300,150 @@ static int m10bmc_sec_bmc_image_load_1(struct m10bmc_sec *sec)
return m10bmc_sec_bmc_image_load(sec, 1);
}
+static int trigger_retimer_eeprom_load(struct m10bmc_sec *sec)
+{
+ struct intel_m10bmc *m10bmc = sec->m10bmc;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_update_bits(m10bmc->regmap,
+ M10BMC_SYS_BASE + M10BMC_DOORBELL,
+ DRBL_PKVL_EEPROM_LOAD_SEC,
+ DRBL_PKVL_EEPROM_LOAD_SEC);
+ if (ret)
+ return ret;
+
+ /*
+ * If the current NIOS FW supports this retimer update feature, then
+ * it will clear the same PKVL_EEPROM_LOAD bit in 2 seconds. Otherwise
+ * the driver needs to clear the PKVL_EEPROM_LOAD bit manually and
+ * return an error code.
+ */
+ ret = regmap_read_poll_timeout(m10bmc->regmap,
+ M10BMC_SYS_BASE + M10BMC_DOORBELL, val,
+ (!(val & DRBL_PKVL_EEPROM_LOAD_SEC)),
+ M10BMC_PKVL_LOAD_INTERVAL_US,
+ M10BMC_PKVL_LOAD_TIMEOUT_US);
+ if (ret == -ETIMEDOUT) {
+ dev_err(sec->dev, "PKVL_EEPROM_LOAD clear timedout\n");
+ regmap_update_bits(m10bmc->regmap,
+ M10BMC_SYS_BASE + M10BMC_DOORBELL,
+ DRBL_PKVL_EEPROM_LOAD_SEC, 0);
+ ret = -ENODEV;
+ } else if (ret) {
+ dev_err(sec->dev, "poll EEPROM_LOAD error %d\n", ret);
+ }
+
+ return ret;
+}
+
+static int poll_retimer_eeprom_load_done(struct m10bmc_sec *sec)
+{
+ struct intel_m10bmc *m10bmc = sec->m10bmc;
+ unsigned int doorbell;
+ int ret;
+
+ /*
+ * RSU_STAT_PKVL_REJECT indicates that the current image is
+ * already programmed. RSU_PROG_PKVL_PROM_DONE that the firmware
+ * update process has finished, but does not necessarily indicate
+ * a successful update.
+ */
+ ret = regmap_read_poll_timeout(m10bmc->regmap,
+ M10BMC_SYS_BASE + M10BMC_DOORBELL,
+ doorbell,
+ ((rsu_prog(doorbell) ==
+ RSU_PROG_PKVL_PROM_DONE) ||
+ (rsu_stat(doorbell) ==
+ RSU_STAT_PKVL_REJECT)),
+ M10BMC_PKVL_PRELOAD_INTERVAL_US,
+ M10BMC_PKVL_PRELOAD_TIMEOUT_US);
+ if (ret) {
+ if (ret == -ETIMEDOUT)
+ dev_err(sec->dev,
+ "Doorbell check timedout: 0x%08x\n", doorbell);
+ else
+ dev_err(sec->dev, "poll Doorbell error\n");
+ return ret;
+ }
+
+ if (rsu_stat(doorbell) == RSU_STAT_PKVL_REJECT) {
+ dev_err(sec->dev, "duplicate image rejected\n");
+ return -ECANCELED;
+ }
+
+ return 0;
+}
+
+static int poll_retimer_preload_done(struct m10bmc_sec *sec)
+{
+ struct intel_m10bmc *m10bmc = sec->m10bmc;
+ unsigned int val;
+ int ret;
+
+ /*
+ * Wait for the updated firmware to be loaded by the PKVL device
+ * and confirm that the updated firmware is operational
+ */
+ ret = regmap_read_poll_timeout(m10bmc->regmap,
+ M10BMC_SYS_BASE + M10BMC_PKVL_POLL_CTRL, val,
+ ((val & M10BMC_PKVL_PRELOAD) == M10BMC_PKVL_PRELOAD),
+ M10BMC_PKVL_PRELOAD_INTERVAL_US,
+ M10BMC_PKVL_PRELOAD_TIMEOUT_US);
+ if (ret) {
+ dev_err(sec->dev, "poll M10BMC_PKVL_PRELOAD error %d\n", ret);
+ return ret;
+ }
+
+ if ((val & M10BMC_PKVL_UPG_STATUS_MASK) != M10BMC_PKVL_UPG_STATUS_GOOD) {
+ dev_err(sec->dev, "error detected during upgrade\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int retimer_check_idle(struct m10bmc_sec *sec)
+{
+ u32 doorbell;
+ int ret;
+
+ ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell);
+ if (ret)
+ return -EIO;
+
+ if (rsu_prog(doorbell) != RSU_PROG_IDLE &&
+ rsu_prog(doorbell) != RSU_PROG_RSU_DONE &&
+ rsu_prog(doorbell) != RSU_PROG_PKVL_PROM_DONE) {
+ log_error_regs(sec, doorbell);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int m10bmc_sec_retimer_eeprom_load(struct m10bmc_sec *sec)
+{
+ int ret;
+
+ ret = retimer_check_idle(sec);
+ if (ret)
+ goto exit;
+
+ ret = trigger_retimer_eeprom_load(sec);
+ if (ret)
+ goto exit;
+
+ ret = poll_retimer_eeprom_load_done(sec);
+ if (ret)
+ goto exit;
+
+ ret = poll_retimer_preload_done(sec);
+
+exit:
+ return ret;
+}
+
static struct image_load m10bmc_image_load_hndlrs[] = {
{
.name = "bmc_factory",
@@ -309,6 +453,10 @@ static struct image_load m10bmc_image_load_hndlrs[] = {
.name = "bmc_user",
.load_image = m10bmc_sec_bmc_image_load_0,
},
+ {
+ .name = "retimer_fw",
+ .load_image = m10bmc_sec_retimer_eeprom_load,
+ },
{}
};
@@ -36,6 +36,37 @@
#define M10BMC_VER_PCB_INFO_MSK GENMASK(31, 24)
#define M10BMC_VER_LEGACY_INVALID 0xffffffff
+/* Retimer related registers, in system register region */
+#define M10BMC_PKVL_POLL_CTRL 0x80
+#define M10BMC_PKVL_A_PRELOAD BIT(16)
+#define M10BMC_PKVL_A_PRELOAD_TO BIT(17)
+#define M10BMC_PKVL_A_DATA_TOO_BIG BIT(18)
+#define M10BMC_PKVL_A_HDR_CKSUM BIT(20)
+#define M10BMC_PKVL_B_PRELOAD BIT(24)
+#define M10BMC_PKVL_B_PRELOAD_TO BIT(25)
+#define M10BMC_PKVL_B_DATA_TOO_BIG BIT(26)
+#define M10BMC_PKVL_B_HDR_CKSUM BIT(28)
+
+#define M10BMC_PKVL_PRELOAD (M10BMC_PKVL_A_PRELOAD | M10BMC_PKVL_B_PRELOAD)
+#define M10BMC_PKVL_PRELOAD_TIMEOUT (M10BMC_PKVL_A_PRELOAD_TO | \
+ M10BMC_PKVL_B_PRELOAD_TO)
+#define M10BMC_PKVL_DATA_TOO_BIG (M10BMC_PKVL_A_DATA_TOO_BIG | \
+ M10BMC_PKVL_B_DATA_TOO_BIG)
+#define M10BMC_PKVL_HDR_CHECKSUM (M10BMC_PKVL_A_HDR_CKSUM | \
+ M10BMC_PKVL_B_HDR_CKSUM)
+
+#define M10BMC_PKVL_UPG_STATUS_MASK (M10BMC_PKVL_PRELOAD | M10BMC_PKVL_PRELOAD_TIMEOUT |\
+ M10BMC_PKVL_DATA_TOO_BIG | M10BMC_PKVL_HDR_CHECKSUM)
+#define M10BMC_PKVL_UPG_STATUS_GOOD (M10BMC_PKVL_PRELOAD | M10BMC_PKVL_HDR_CHECKSUM)
+
+/* interval 100ms and timeout 2s */
+#define M10BMC_PKVL_LOAD_INTERVAL_US (100 * 1000)
+#define M10BMC_PKVL_LOAD_TIMEOUT_US (2 * 1000 * 1000)
+
+/* interval 100ms and timeout 30s */
+#define M10BMC_PKVL_PRELOAD_INTERVAL_US (100 * 1000)
+#define M10BMC_PKVL_PRELOAD_TIMEOUT_US (30 * 1000 * 1000)
+
/* Secure update doorbell register, in system register region */
#define M10BMC_DOORBELL 0x400