diff mbox series

[v1,3/3] fpga: m10bmc-sec: add m10bmc_sec_retimer_load callback

Message ID 20220705080058.92532-4-tianfei.zhang@intel.com (mailing list archive)
State New
Headers show
Series add Intel FPGA image loader support | expand

Commit Message

Zhang, Tianfei July 5, 2022, 8 a.m. UTC
From: Russ Weight <russell.h.weight@intel.com>

Create m10bmc_sec_retimer_load() callback function
to provide a trigger to update a new retimer (Intel
C827 Ethernet transceiver) firmware on Intel PAC
N3000 Card.

Signed-off-by: Russ Weight <russell.h.weight@intel.com>
Signed-off-by: Tianfei Zhang <tianfei.zhang@intel.com>
---
 drivers/fpga/intel-m10-bmc-sec-update.c | 146 ++++++++++++++++++++++++
 include/linux/mfd/intel-m10-bmc.h       |  31 +++++
 2 files changed, 177 insertions(+)

Comments

Xu Yilun July 7, 2022, 2:05 a.m. UTC | #1
On Tue, Jul 05, 2022 at 04:00:58AM -0400, Tianfei Zhang wrote:
> From: Russ Weight <russell.h.weight@intel.com>
> 
> Create m10bmc_sec_retimer_load() callback function
> to provide a trigger to update a new retimer (Intel
> C827 Ethernet transceiver) firmware on Intel PAC

A firmware loader framework is upstreamed recently, does that meet your
need?

Thanks,
Yilun

> N3000 Card.
> 
> Signed-off-by: Russ Weight <russell.h.weight@intel.com>
> Signed-off-by: Tianfei Zhang <tianfei.zhang@intel.com>
> ---
>  drivers/fpga/intel-m10-bmc-sec-update.c | 146 ++++++++++++++++++++++++
>  include/linux/mfd/intel-m10-bmc.h       |  31 +++++
>  2 files changed, 177 insertions(+)
> 
> diff --git a/drivers/fpga/intel-m10-bmc-sec-update.c b/drivers/fpga/intel-m10-bmc-sec-update.c
> index 640690e6c131..707dfd8a1931 100644
> --- a/drivers/fpga/intel-m10-bmc-sec-update.c
> +++ b/drivers/fpga/intel-m10-bmc-sec-update.c
> @@ -299,6 +299,148 @@ 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 = m10bmc_sys_update_bits(m10bmc, 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");
> +		m10bmc_sys_update_bits(m10bmc, 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",
> @@ -308,6 +450,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,
> +	},
>  	{}
>  };
>  
> diff --git a/include/linux/mfd/intel-m10-bmc.h b/include/linux/mfd/intel-m10-bmc.h
> index 8e434886a5a1..23025e4d2779 100644
> --- a/include/linux/mfd/intel-m10-bmc.h
> +++ b/include/linux/mfd/intel-m10-bmc.h
> @@ -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
>  
> -- 
> 2.26.2
Zhang, Tianfei July 7, 2022, 6:26 a.m. UTC | #2
> -----Original Message-----
> From: Xu, Yilun <yilun.xu@intel.com>
> Sent: Thursday, July 7, 2022 10:06 AM
> To: Zhang, Tianfei <tianfei.zhang@intel.com>
> Cc: mdf@kernel.org; lee.jones@linaro.org; Weight, Russell H
> <russell.h.weight@intel.com>; Wu, Hao <hao.wu@intel.com>;
> trix@redhat.com; linux-fpga@vger.kernel.org
> Subject: Re: [PATCH v1 3/3] fpga: m10bmc-sec: add m10bmc_sec_retimer_load
> callback
> 
> On Tue, Jul 05, 2022 at 04:00:58AM -0400, Tianfei Zhang wrote:
> > From: Russ Weight <russell.h.weight@intel.com>
> >
> > Create m10bmc_sec_retimer_load() callback function to provide a
> > trigger to update a new retimer (Intel
> > C827 Ethernet transceiver) firmware on Intel PAC
> 
> A firmware loader framework is upstreamed recently, does that meet your
> need?

You mean that the firmware loader Russ has submitted? We have used this firmware loader framework to flash the M10 BMC image.
The retimer firmware was contained into M10 BMC image, this patch trigger the retimer firmware reload.

> 
> Thanks,
> Yilun
> 
> > N3000 Card.
> >
> > Signed-off-by: Russ Weight <russell.h.weight@intel.com>
> > Signed-off-by: Tianfei Zhang <tianfei.zhang@intel.com>
> > ---
> >  drivers/fpga/intel-m10-bmc-sec-update.c | 146 ++++++++++++++++++++++++
> >  include/linux/mfd/intel-m10-bmc.h       |  31 +++++
> >  2 files changed, 177 insertions(+)
> >
> > diff --git a/drivers/fpga/intel-m10-bmc-sec-update.c
> > b/drivers/fpga/intel-m10-bmc-sec-update.c
> > index 640690e6c131..707dfd8a1931 100644
> > --- a/drivers/fpga/intel-m10-bmc-sec-update.c
> > +++ b/drivers/fpga/intel-m10-bmc-sec-update.c
> > @@ -299,6 +299,148 @@ 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 = m10bmc_sys_update_bits(m10bmc, 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");
> > +		m10bmc_sys_update_bits(m10bmc, 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",
> > @@ -308,6 +450,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,
> > +	},
> >  	{}
> >  };
> >
> > diff --git a/include/linux/mfd/intel-m10-bmc.h
> > b/include/linux/mfd/intel-m10-bmc.h
> > index 8e434886a5a1..23025e4d2779 100644
> > --- a/include/linux/mfd/intel-m10-bmc.h
> > +++ b/include/linux/mfd/intel-m10-bmc.h
> > @@ -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
> >
> > --
> > 2.26.2
diff mbox series

Patch

diff --git a/drivers/fpga/intel-m10-bmc-sec-update.c b/drivers/fpga/intel-m10-bmc-sec-update.c
index 640690e6c131..707dfd8a1931 100644
--- a/drivers/fpga/intel-m10-bmc-sec-update.c
+++ b/drivers/fpga/intel-m10-bmc-sec-update.c
@@ -299,6 +299,148 @@  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 = m10bmc_sys_update_bits(m10bmc, 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");
+		m10bmc_sys_update_bits(m10bmc, 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",
@@ -308,6 +450,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,
+	},
 	{}
 };
 
diff --git a/include/linux/mfd/intel-m10-bmc.h b/include/linux/mfd/intel-m10-bmc.h
index 8e434886a5a1..23025e4d2779 100644
--- a/include/linux/mfd/intel-m10-bmc.h
+++ b/include/linux/mfd/intel-m10-bmc.h
@@ -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