From 2fc3237e1398dc81f66842461db2f4a5466df6bf Mon Sep 17 00:00:00 2001 From: Tze Yee Ng Date: Thu, 14 May 2026 19:11:57 -0700 Subject: [PATCH 1/5] Documentation: ABI: add stratix10-rsu QSPI size and erase_size sysfs Document the eight read-only sysfs files (size0-size3, erase_size0- erase_size3) under platform/stratix10-rsu: flash size and erase size in bytes, 32-bit hex per line, EIO when the value is not available. Signed-off-by: Tze Yee Ng Signed-off-by: Dinh Nguyen --- .../sysfs-devices-platform-stratix10-rsu | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-devices-platform-stratix10-rsu b/Documentation/ABI/testing/sysfs-devices-platform-stratix10-rsu index ee253b033280..b93341e4471d 100644 --- a/Documentation/ABI/testing/sysfs-devices-platform-stratix10-rsu +++ b/Documentation/ABI/testing/sysfs-devices-platform-stratix10-rsu @@ -172,3 +172,61 @@ Contact: Richard Gong Description: (RO) max retry parameter is stored in the firmware decision IO section, as a byte located at offset 0x18c. + +What: /sys/devices/platform/stratix10-rsu.0/size0 +Date: May 2026 +KernelVersion: 7.1 +Contact: Tze Yee Ng +Description: + (RO) Flash size in bytes for QSPI device 0 (32-bit hex, one + line). EIO if unavailable. + +What: /sys/devices/platform/stratix10-rsu.0/size1 +Date: May 2026 +KernelVersion: 7.1 +Contact: Tze Yee Ng +Description: + (RO) Like size0 for device 1. + +What: /sys/devices/platform/stratix10-rsu.0/size2 +Date: May 2026 +KernelVersion: 7.1 +Contact: Tze Yee Ng +Description: + (RO) Like size0 for device 2. + +What: /sys/devices/platform/stratix10-rsu.0/size3 +Date: May 2026 +KernelVersion: 7.1 +Contact: Tze Yee Ng +Description: + (RO) Like size0 for device 3. + +What: /sys/devices/platform/stratix10-rsu.0/erase_size0 +Date: May 2026 +KernelVersion: 7.1 +Contact: Tze Yee Ng +Description: + (RO) Erase size in bytes for QSPI device 0 (32-bit hex, one + line). EIO if unavailable. + +What: /sys/devices/platform/stratix10-rsu.0/erase_size1 +Date: May 2026 +KernelVersion: 7.1 +Contact: Tze Yee Ng +Description: + (RO) Like erase_size0 for device 1. + +What: /sys/devices/platform/stratix10-rsu.0/erase_size2 +Date: May 2026 +KernelVersion: 7.1 +Contact: Tze Yee Ng +Description: + (RO) Like erase_size0 for device 2. + +What: /sys/devices/platform/stratix10-rsu.0/erase_size3 +Date: May 2026 +KernelVersion: 7.1 +Contact: Tze Yee Ng +Description: + (RO) Like erase_size0 for device 3. From fe8c977b35fd9e945ff281382c808026d41731e5 Mon Sep 17 00:00:00 2001 From: Tze Yee Ng Date: Thu, 14 May 2026 19:11:58 -0700 Subject: [PATCH 2/5] firmware: stratix10-rsu: Add flash device info retrieval via SMC Extend the Intel Remote System Update (RSU) driver to retrieve the device info table through an ARM SMC call to the service layer. The table reports flash size and erase size for multiple devices. Signed-off-by: Tze Yee Ng Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-rsu.c | 200 +++++++++++++++++- drivers/firmware/stratix10-svc.c | 94 +++++++- include/linux/firmware/intel/stratix10-smc.h | 25 ++- .../firmware/intel/stratix10-svc-client.h | 12 +- 4 files changed, 315 insertions(+), 16 deletions(-) diff --git a/drivers/firmware/stratix10-rsu.c b/drivers/firmware/stratix10-rsu.c index daddb5224794..d887c74b9821 100644 --- a/drivers/firmware/stratix10-rsu.c +++ b/drivers/firmware/stratix10-rsu.c @@ -7,17 +7,24 @@ #include #include #include +#include +#include +#include #include #include #include #include #include -#include #include #include -#include -#define RSU_ERASE_SIZE_MASK GENMASK_ULL(63, 32) +/* + * INTEL_SIP_SMC_RSU_GET_DEVICE_INFO packs each flash word as: + * [63:32] erase_size, [31:0] size (see stratix10-smc.h). + */ +#define RSU_DEVICE_INFO_SIZE_MASK GENMASK_ULL(31, 0) +#define RSU_DEVICE_INFO_ERASE_SIZE_MASK GENMASK_ULL(63, 32) + #define RSU_DCMF0_MASK GENMASK_ULL(31, 0) #define RSU_DCMF1_MASK GENMASK_ULL(63, 32) #define RSU_DCMF2_MASK GENMASK_ULL(31, 0) @@ -33,11 +40,31 @@ #define INVALID_DCMF_VERSION 0xFF #define INVALID_DCMF_STATUS 0xFFFFFFFF #define INVALID_SPT_ADDRESS 0x0 +#define INVALID_DEVICE_INFO (~0U) #define RSU_RETRY_SLEEP_MS (1U) #define RSU_ASYNC_MSG_RETRY (3U) #define RSU_GET_SPT_RESP_LEN (4 * sizeof(unsigned int)) +struct flash_device_info { + unsigned int size; + unsigned int erase_size; +}; + +/** + * rsu_device_info_set_from_packed() - Decode one RSU device-info SMC word + * @di: slot to fill + * @packed: register value: [63:32] erase_size, [31:0] size + * (INTEL_SIP_SMC_RSU_GET_DEVICE_INFO) + */ +static void rsu_device_info_set_from_packed(struct flash_device_info *di, + unsigned long packed) +{ + di->size = (unsigned int)FIELD_GET(RSU_DEVICE_INFO_SIZE_MASK, packed); + di->erase_size = (unsigned int)FIELD_GET(RSU_DEVICE_INFO_ERASE_SIZE_MASK, + packed); +} + typedef void (*rsu_callback)(struct stratix10_svc_client *client, struct stratix10_svc_cb_data *data); /** @@ -60,6 +87,8 @@ typedef void (*rsu_callback)(struct stratix10_svc_client *client, * @dcmf_status.dcmf1: dcmf1 status * @dcmf_status.dcmf2: dcmf2 status * @dcmf_status.dcmf3: dcmf3 status + * @device_info: per-device flash information array; each entry contains + * size and erase size for one flash device * @retry_counter: the current image's retry counter * @max_retry: the preset max retry value * @spt0_address: address of spt0 @@ -93,6 +122,8 @@ struct stratix10_rsu_priv { unsigned int dcmf3; } dcmf_status; + struct flash_device_info device_info[4]; + unsigned int retry_counter; unsigned int max_retry; @@ -100,6 +131,20 @@ struct stratix10_rsu_priv { unsigned long spt1_address; }; +/** + * rsu_device_info_invalidate() - Mark all cached QSPI device slots invalid + * @priv: RSU private data + */ +static void rsu_device_info_invalidate(struct stratix10_rsu_priv *priv) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(priv->device_info); i++) { + priv->device_info[i].size = INVALID_DEVICE_INFO; + priv->device_info[i].erase_size = INVALID_DEVICE_INFO; + } +} + typedef void (*rsu_async_callback)(struct device *dev, struct stratix10_rsu_priv *priv, struct stratix10_svc_cb_data *data); @@ -229,8 +274,57 @@ static void rsu_dcmf_status_callback(struct stratix10_svc_client *client, } /** - * rsu_async_get_spt_table_callback() - Callback to be used by the rsu_async_send() - * to retrieve the SPT table information. + * rsu_get_device_info_callback() - Callback from Intel service layer for + * getting the QSPI device info + * @client: pointer to client + * @data: pointer to callback data structure + * + * Callback from Intel service layer for QSPI device info. + * @data->kaddr1 points to struct arm_smccc_1_2_regs on SVC_STATUS_OK or + * SVC_STATUS_ERROR; it is NULL on SVC_STATUS_NO_SUPPORT (unsupported command). + */ +static void rsu_get_device_info_callback(struct stratix10_svc_client *client, + struct stratix10_svc_cb_data *data) +{ + struct stratix10_rsu_priv *priv = client->priv; + struct arm_smccc_1_2_regs *res = data->kaddr1; + + if (data->status == BIT(SVC_STATUS_OK)) { + if (!res) { + dev_err(client->dev, + "COMMAND_RSU_GET_DEVICE_INFO: missing result payload\n"); + rsu_device_info_invalidate(priv); + complete(&priv->completion); + return; + } + + rsu_device_info_set_from_packed(&priv->device_info[0], res->a1); + rsu_device_info_set_from_packed(&priv->device_info[1], res->a2); + rsu_device_info_set_from_packed(&priv->device_info[2], res->a3); + rsu_device_info_set_from_packed(&priv->device_info[3], res->a4); + + } else if (data->status == BIT(SVC_STATUS_NO_SUPPORT)) { + dev_warn(client->dev, + "COMMAND_RSU_GET_DEVICE_INFO not supported by firmware\n"); + rsu_device_info_invalidate(priv); + } else { + if (res) + dev_err(client->dev, + "COMMAND_RSU_GET_DEVICE_INFO returned 0x%lX\n", + res->a0); + else + dev_err(client->dev, + "COMMAND_RSU_GET_DEVICE_INFO failed with status 0x%X\n", + data->status); + rsu_device_info_invalidate(priv); + } + + complete(&priv->completion); +} + +/** + * rsu_async_get_spt_table_callback() - Callback to be used by the + * rsu_async_send() to retrieve the SPT table information. * @dev: pointer to device object * @priv: pointer to priv object * @data: pointer to callback data structure @@ -698,6 +792,75 @@ static ssize_t notify_store(struct device *dev, return count; } +static ssize_t rsu_device_info_show(struct device *dev, char *buf, + unsigned int index, bool erase_size) +{ + struct stratix10_rsu_priv *priv = dev_get_drvdata(dev); + unsigned int value; + + if (!priv) + return -ENODEV; + + if (index >= ARRAY_SIZE(priv->device_info)) + return -EINVAL; + + value = erase_size ? priv->device_info[index].erase_size : + priv->device_info[index].size; + + if (value == INVALID_DEVICE_INFO) + return -EIO; + + return sysfs_emit(buf, "0x%08x\n", value); +} + +static ssize_t size0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return rsu_device_info_show(dev, buf, 0, false); +} + +static ssize_t size1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return rsu_device_info_show(dev, buf, 1, false); +} + +static ssize_t size2_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return rsu_device_info_show(dev, buf, 2, false); +} + +static ssize_t size3_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return rsu_device_info_show(dev, buf, 3, false); +} + +static ssize_t erase_size0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return rsu_device_info_show(dev, buf, 0, true); +} + +static ssize_t erase_size1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return rsu_device_info_show(dev, buf, 1, true); +} + +static ssize_t erase_size2_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return rsu_device_info_show(dev, buf, 2, true); +} + +static ssize_t erase_size3_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return rsu_device_info_show(dev, buf, 3, true); +} + static ssize_t spt0_address_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -742,6 +905,14 @@ static DEVICE_ATTR_RO(dcmf0_status); static DEVICE_ATTR_RO(dcmf1_status); static DEVICE_ATTR_RO(dcmf2_status); static DEVICE_ATTR_RO(dcmf3_status); +static DEVICE_ATTR_RO(size0); +static DEVICE_ATTR_RO(size1); +static DEVICE_ATTR_RO(size2); +static DEVICE_ATTR_RO(size3); +static DEVICE_ATTR_RO(erase_size0); +static DEVICE_ATTR_RO(erase_size1); +static DEVICE_ATTR_RO(erase_size2); +static DEVICE_ATTR_RO(erase_size3); static DEVICE_ATTR_WO(reboot_image); static DEVICE_ATTR_WO(notify); static DEVICE_ATTR_RO(spt0_address); @@ -764,6 +935,14 @@ static struct attribute *rsu_attrs[] = { &dev_attr_dcmf1_status.attr, &dev_attr_dcmf2_status.attr, &dev_attr_dcmf3_status.attr, + &dev_attr_size0.attr, + &dev_attr_size1.attr, + &dev_attr_size2.attr, + &dev_attr_size3.attr, + &dev_attr_erase_size0.attr, + &dev_attr_erase_size1.attr, + &dev_attr_erase_size2.attr, + &dev_attr_erase_size3.attr, &dev_attr_reboot_image.attr, &dev_attr_notify.attr, &dev_attr_spt0_address.attr, @@ -796,6 +975,7 @@ static int stratix10_rsu_probe(struct platform_device *pdev) priv->dcmf_status.dcmf2 = INVALID_DCMF_STATUS; priv->dcmf_status.dcmf3 = INVALID_DCMF_STATUS; /* spt0/1_address and status fields default to 0 from kzalloc */ + rsu_device_info_invalidate(priv); mutex_init(&priv->lock); init_completion(&priv->completion); @@ -846,6 +1026,16 @@ static int stratix10_rsu_probe(struct platform_device *pdev) goto remove_async_client; } + /* get QSPI device info from firmware */ + ret = rsu_send_msg(priv, COMMAND_RSU_GET_DEVICE_INFO, 0, + rsu_get_device_info_callback); + if (ret) { + dev_err(dev, "Error, getting QSPI Device Info %i\n", ret); + stratix10_svc_remove_async_client(priv->chan); + stratix10_svc_free_channel(priv->chan); + return ret; + } + ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_GET_SPT_TABLE, 0, rsu_async_get_spt_table_callback); if (ret) { diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index c24ca5823078..de938ab2db0b 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -445,13 +446,15 @@ static void svc_thread_cmd_config_status(struct stratix10_svc_controller *ctrl, * svc_thread_recv_status_ok() - handle the successful status * @p_data: pointer to service data structure * @cb_data: pointer to callback data structure to service client - * @res: result from SMC or HVC call + * @res: result from SMC or HVC call (a0-a3; used for routing and most commands) + * @res12: full v1.2 result for %COMMAND_RSU_GET_DEVICE_INFO, else NULL * * Send back the correspond status to the service clients. */ static void svc_thread_recv_status_ok(struct stratix10_svc_data *p_data, struct stratix10_svc_cb_data *cb_data, - struct arm_smccc_res res) + struct arm_smccc_res res, + struct arm_smccc_1_2_regs *res12) { cb_data->kaddr1 = NULL; cb_data->kaddr2 = NULL; @@ -513,6 +516,16 @@ static void svc_thread_recv_status_ok(struct stratix10_svc_data *p_data, res.a2 = res.a2 * BYTE_TO_WORD_SIZE; cb_data->kaddr2 = &res.a2; break; + case COMMAND_RSU_GET_DEVICE_INFO: + if (WARN_ON(!res12)) { + cb_data->status = BIT(SVC_STATUS_ERROR); + break; + } + cb_data->status = BIT(SVC_STATUS_OK); + cb_data->kaddr1 = res12; + cb_data->kaddr2 = NULL; + cb_data->kaddr3 = NULL; + break; default: pr_warn("it shouldn't happen\n"); break; @@ -522,6 +535,10 @@ static void svc_thread_recv_status_ok(struct stratix10_svc_data *p_data, p_data->chan->scl->receive_cb(p_data->chan->scl, cb_data); } +static void svc_smccc_1_2_full(struct stratix10_svc_controller *ctrl, + const struct arm_smccc_1_2_regs *args, + struct arm_smccc_1_2_regs *res); + /** * svc_normal_to_secure_thread() - the function to run in the kthread * @data: data pointer for kthread function @@ -539,6 +556,7 @@ static int svc_normal_to_secure_thread(void *data) struct stratix10_svc_data *pdata = NULL; struct stratix10_svc_cb_data *cbdata = NULL; struct arm_smccc_res res; + struct arm_smccc_1_2_regs res12 = { 0 }; unsigned long a0, a1, a2, a3, a4, a5, a6, a7; int ret_fifo = 0; @@ -727,6 +745,16 @@ static int svc_normal_to_secure_thread(void *data) a5 = (unsigned long)pdata->paddr_output; a6 = (unsigned long)pdata->size_output / BYTE_TO_WORD_SIZE; break; + case COMMAND_RSU_GET_DEVICE_INFO: + a0 = INTEL_SIP_SMC_RSU_GET_DEVICE_INFO; + a1 = 0; + a2 = 0; + a3 = 0; + a4 = 0; + a5 = 0; + a6 = 0; + a7 = 0; + break; default: pr_warn("it shouldn't happen\n"); mutex_unlock(&ctrl->sdm_lock); @@ -740,7 +768,18 @@ static int svc_normal_to_secure_thread(void *data) pr_debug(" a3=0x%016x\n", (unsigned int)a3); pr_debug(" a4=0x%016x\n", (unsigned int)a4); pr_debug(" a5=0x%016x\n", (unsigned int)a5); - ctrl->invoke_fn(a0, a1, a2, a3, a4, a5, a6, a7, &res); + if (pdata->command == COMMAND_RSU_GET_DEVICE_INFO) { + struct arm_smccc_1_2_regs args12 = { 0 }; + + args12.a0 = INTEL_SIP_SMC_RSU_GET_DEVICE_INFO; + svc_smccc_1_2_full(ctrl, &args12, &res12); + res.a0 = res12.a0; + res.a1 = res12.a1; + res.a2 = res12.a2; + res.a3 = res12.a3; + } else { + ctrl->invoke_fn(a0, a1, a2, a3, a4, a5, a6, a7, &res); + } pr_debug("%s: %s: after SMC call -- res.a0=0x%016x", __func__, chan->name, (unsigned int)res.a0); @@ -763,9 +802,15 @@ static int svc_normal_to_secure_thread(void *data) } switch (res.a0) { - case INTEL_SIP_SMC_STATUS_OK: - svc_thread_recv_status_ok(pdata, cbdata, res); + case INTEL_SIP_SMC_STATUS_OK: { + struct arm_smccc_1_2_regs *devinfo_res = + (pdata->command == COMMAND_RSU_GET_DEVICE_INFO) ? + &res12 : NULL; + + svc_thread_recv_status_ok(pdata, cbdata, res, + devinfo_res); break; + } case INTEL_SIP_SMC_STATUS_BUSY: switch (pdata->command) { case COMMAND_RECONFIG_DATA_SUBMIT: @@ -806,10 +851,16 @@ static int svc_normal_to_secure_thread(void *data) case INTEL_SIP_SMC_RSU_ERROR: pr_err("%s: STATUS_ERROR\n", __func__); cbdata->status = BIT(SVC_STATUS_ERROR); - cbdata->kaddr1 = &res.a1; - cbdata->kaddr2 = (res.a2) ? - svc_pa_to_va(res.a2) : NULL; - cbdata->kaddr3 = (res.a3) ? &res.a3 : NULL; + if (pdata->command == COMMAND_RSU_GET_DEVICE_INFO) { + cbdata->kaddr1 = &res12; + cbdata->kaddr2 = NULL; + cbdata->kaddr3 = NULL; + } else { + cbdata->kaddr1 = &res.a1; + cbdata->kaddr2 = (res.a2) ? + svc_pa_to_va(res.a2) : NULL; + cbdata->kaddr3 = (res.a3) ? &res.a3 : NULL; + } pdata->chan->scl->receive_cb(pdata->chan->scl, cbdata); break; default: @@ -1025,6 +1076,31 @@ static void svc_smccc_hvc(unsigned long a0, unsigned long a1, arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res); } +/** + * svc_smccc_1_2_full() - SMC/HVC v1.2 call matching the sync channel method + * @ctrl: service controller (selects SMC vs HVC) + * @args: arguments + * @res: full register-file result (a0-a17) + */ +static void svc_smccc_1_2_full(struct stratix10_svc_controller *ctrl, + const struct arm_smccc_1_2_regs *args, + struct arm_smccc_1_2_regs *res) +{ + if (ctrl->invoke_fn == svc_smccc_smc) { + arm_smccc_1_2_smc(args, res); + } else if (ctrl->invoke_fn == svc_smccc_hvc) { + arm_smccc_1_2_hvc(args, res); + } else { + WARN_ON_ONCE(1); + /* + * INTEL_SIP_SMC_STATUS_OK is 0; zero-filled res would be misrouted + * as success. Force an error path and clear fabricated payload. + */ + memset(res, 0, sizeof(*res)); + res->a0 = INTEL_SIP_SMC_STATUS_ERROR; + } +} + /** * get_invoke_func() - invoke SMC or HVC call * @dev: pointer to device diff --git a/include/linux/firmware/intel/stratix10-smc.h b/include/linux/firmware/intel/stratix10-smc.h index 9116512169dc..6e042943b6ce 100644 --- a/include/linux/firmware/intel/stratix10-smc.h +++ b/include/linux/firmware/intel/stratix10-smc.h @@ -429,6 +429,29 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE) #define INTEL_SIP_SMC_RSU_DCMF_STATUS \ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_RSU_DCMF_STATUS) +/** + * Request INTEL_SIP_SMC_RSU_GET_DEVICE_INFO + * + * Sync call used by service driver at EL1 to query QSPI device info from FW + * + * Call register usage: + * a0 INTEL_SIP_SMC_RSU_GET_DEVICE_INFO + * a1-7 not used + * + * Return status + * a0 INTEL_SIP_SMC_STATUS_OK + * a1 erasesize0 | size0 + * a2 erasesize1 | size1 + * a3 erasesize2 | size2 + * a4 erasesize3 | size3 + * Or + * + * a0 INTEL_SIP_SMC_RSU_ERROR + */ +#define INTEL_SIP_SMC_FUNCID_RSU_GET_DEVICE_INFO 22 +#define INTEL_SIP_SMC_RSU_GET_DEVICE_INFO \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_RSU_GET_DEVICE_INFO) + /** * Request INTEL_SIP_SMC_SERVICE_COMPLETED * Sync call to check if the secure world have completed service request @@ -493,7 +516,7 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE) * a3 not used */ #define INTEL_SIP_SMC_FUNCID_MBOX_SEND_CMD 60 - #define INTEL_SIP_SMC_MBOX_SEND_CMD \ +#define INTEL_SIP_SMC_MBOX_SEND_CMD \ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_MBOX_SEND_CMD) /** diff --git a/include/linux/firmware/intel/stratix10-svc-client.h b/include/linux/firmware/intel/stratix10-svc-client.h index 3edd93502bf8..af13dacdf5ac 100644 --- a/include/linux/firmware/intel/stratix10-svc-client.h +++ b/include/linux/firmware/intel/stratix10-svc-client.h @@ -128,6 +128,10 @@ struct stratix10_svc_chan; * @COMMAND_RSU_DCMF_STATUS: query firmware for the DCMF status * return status is SVC_STATUS_OK or SVC_STATUS_ERROR * + * @COMMAND_RSU_GET_DEVICE_INFO: query firmware for QSPI device info; + * return status is SVC_STATUS_OK, SVC_STATUS_ERROR, or SVC_STATUS_NO_SUPPORT + * (unsupported command / firmware compatibility path in the service layer). + * * @COMMAND_RSU_GET_SPT_TABLE: query firmware for SPT table * return status is SVC_STATUS_OK or SVC_STATUS_ERROR * @@ -174,6 +178,7 @@ enum stratix10_svc_command_code { COMMAND_RSU_MAX_RETRY, COMMAND_RSU_DCMF_VERSION, COMMAND_RSU_DCMF_STATUS, + COMMAND_RSU_GET_DEVICE_INFO, COMMAND_FIRMWARE_VERSION, COMMAND_RSU_GET_SPT_TABLE, /* for FCS */ @@ -224,7 +229,12 @@ struct stratix10_svc_command_config_type { /** * struct stratix10_svc_cb_data - callback data structure from service layer * @status: the status of sent command - * @kaddr1: address of 1st completed data block + * @kaddr1: address of 1st completed data block, or command-specific payload. + * For COMMAND_RSU_GET_DEVICE_INFO on SVC_STATUS_OK or SVC_STATUS_ERROR, + * points to struct arm_smccc_1_2_regs filled by the SMC/HVC return + * registers (a0 status, a1-a4 packed device words per + * INTEL_SIP_SMC_RSU_GET_DEVICE_INFO). On SVC_STATUS_NO_SUPPORT (older + * firmware that does not handle this command), kaddr1 is NULL. * @kaddr2: address of 2nd completed data block * @kaddr3: address of 3rd completed data block */ From ef7453e1f986813d8bf734af33088e34bf91f70a Mon Sep 17 00:00:00 2001 From: Mahesh Rao Date: Fri, 10 Jul 2026 00:04:40 -0700 Subject: [PATCH 3/5] firmware: stratix10-rsu: Add synchronous fallback for async SVC operations The RSU driver was migrated to the Stratix10 asynchronous service framework, which assumes async client registration always succeeds. On platforms where the async controller is unavailable or RSU async support is not present, probe fails or sysfs operations cannot reach firmware. Detect async capability at probe via stratix10_svc_add_async_client() and fall back to the legacy synchronous rsu_send_msg() path when registration fails. Track capability in priv->async and branch accordingly for: - Initial RSU status and retry counter during probe (retry is bundled in async status; issue COMMAND_RSU_RETRY separately in sync mode) - notify sysfs store (notify, status, and retry) - SPT table retrieval (COMMAND_RSU_GET_SPT_TABLE async vs mbox COMMAND_MBOX_SEND_CMD synchronous) Restore synchronous callbacks (rsu_status_callback, rsu_retry_callback, rsu_get_spt_callback) and wire COMMAND_MBOX_SEND_CMD payload handling in __rsu_send_msg_locked. Improve probe and remove cleanup: return on intermediate probe failures, remove the async client when registered, and free the SPT response buffer on sync-path allocation errors. Suggested-by: Anders Hedlund Signed-off-by: Mahesh Rao Signed-off-by: Asyraaf Azhar Signed-off-by: Tze Yee Ng Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-rsu.c | 227 ++++++++++++++++++++++++++++--- 1 file changed, 207 insertions(+), 20 deletions(-) diff --git a/drivers/firmware/stratix10-rsu.c b/drivers/firmware/stratix10-rsu.c index d887c74b9821..b360f752d191 100644 --- a/drivers/firmware/stratix10-rsu.c +++ b/drivers/firmware/stratix10-rsu.c @@ -18,6 +18,10 @@ #include #include +#define RSU_STATE_MASK GENMASK_ULL(31, 0) +#define RSU_VERSION_MASK GENMASK_ULL(63, 32) +#define RSU_ERROR_LOCATION_MASK GENMASK_ULL(31, 0) +#define RSU_ERROR_DETAIL_MASK GENMASK_ULL(63, 32) /* * INTEL_SIP_SMC_RSU_GET_DEVICE_INFO packs each flash word as: * [63:32] erase_size, [31:0] size (see stratix10-smc.h). @@ -44,6 +48,7 @@ #define RSU_RETRY_SLEEP_MS (1U) #define RSU_ASYNC_MSG_RETRY (3U) +#define RSU_GET_SPT_CMD 0x5A #define RSU_GET_SPT_RESP_LEN (4 * sizeof(unsigned int)) struct flash_device_info { @@ -73,6 +78,7 @@ typedef void (*rsu_callback)(struct stratix10_svc_client *client, * @client: active service client * @completion: state for callback completion * @lock: a mutex to protect callback completion state + * @async: supports async operations * @status.current_image: address of image currently running in flash * @status.fail_image: address of failed image in flash * @status.version: the interface version number of RSU firmware @@ -93,12 +99,14 @@ typedef void (*rsu_callback)(struct stratix10_svc_client *client, * @max_retry: the preset max retry value * @spt0_address: address of spt0 * @spt1_address: address of spt1 + * @get_spt_response_buf: response from sdm for get_spt command */ struct stratix10_rsu_priv { struct stratix10_svc_chan *chan; struct stratix10_svc_client client; struct completion completion; struct mutex lock; + bool async; struct { unsigned long current_image; unsigned long fail_image; @@ -129,6 +137,8 @@ struct stratix10_rsu_priv { unsigned long spt0_address; unsigned long spt1_address; + + unsigned int *get_spt_response_buf; }; /** @@ -148,6 +158,45 @@ static void rsu_device_info_invalidate(struct stratix10_rsu_priv *priv) typedef void (*rsu_async_callback)(struct device *dev, struct stratix10_rsu_priv *priv, struct stratix10_svc_cb_data *data); +/** + * rsu_status_callback() - Status callback from Intel Service Layer + * @client: pointer to service client + * @data: pointer to callback data structure + * + * Callback from Intel service layer for RSU status request. Status is + * only updated after a system reboot, so a get updated status call is + * made during driver probe. + */ +static void rsu_status_callback(struct stratix10_svc_client *client, + struct stratix10_svc_cb_data *data) +{ + struct stratix10_rsu_priv *priv = client->priv; + struct arm_smccc_res *res = (struct arm_smccc_res *)data->kaddr1; + + if (data->status == BIT(SVC_STATUS_OK)) { + priv->status.version = FIELD_GET(RSU_VERSION_MASK, + res->a2); + priv->status.state = FIELD_GET(RSU_STATE_MASK, res->a2); + priv->status.fail_image = res->a1; + priv->status.current_image = res->a0; + priv->status.error_location = + FIELD_GET(RSU_ERROR_LOCATION_MASK, res->a3); + priv->status.error_details = + FIELD_GET(RSU_ERROR_DETAIL_MASK, res->a3); + } else { + dev_err(client->dev, "COMMAND_RSU_STATUS returned 0x%lX\n", + res->a0); + priv->status.version = 0; + priv->status.state = 0; + priv->status.fail_image = 0; + priv->status.current_image = 0; + priv->status.error_location = 0; + priv->status.error_details = 0; + } + + complete(&priv->completion); +} + /** * rsu_async_status_callback() - Status callback from rsu_async_send() * @dev: pointer to device object @@ -192,6 +241,32 @@ static void rsu_command_callback(struct stratix10_svc_client *client, complete(&priv->completion); } +/** + * rsu_retry_callback() - Callback from Intel service layer for getting + * the current image's retry counter from the firmware + * @client: pointer to client + * @data: pointer to callback data structure + * + * Callback from Intel service layer for retry counter, which is used by + * user to know how many times the images is still allowed to reload + * itself before giving up and starting RSU fail-over flow. + */ +static void rsu_retry_callback(struct stratix10_svc_client *client, + struct stratix10_svc_cb_data *data) +{ + struct stratix10_rsu_priv *priv = client->priv; + unsigned int *counter = (unsigned int *)data->kaddr1; + + if (data->status == BIT(SVC_STATUS_OK)) + priv->retry_counter = *counter; + else if (data->status == BIT(SVC_STATUS_NO_SUPPORT)) + dev_warn(client->dev, "Secure FW doesn't support retry\n"); + else + dev_err(client->dev, "Failed to get retry counter %lu\n", + BIT(data->status)); + + complete(&priv->completion); +} /** * rsu_max_retry_callback() - Callback from Intel service layer for getting @@ -337,6 +412,38 @@ static void rsu_async_get_spt_table_callback(struct device *dev, priv->spt1_address = *((unsigned long *)data->kaddr2); } +static void rsu_get_spt_callback(struct stratix10_svc_client *client, + struct stratix10_svc_cb_data *data) +{ + struct stratix10_rsu_priv *priv = client->priv; + unsigned long *mbox_err = (unsigned long *)data->kaddr1; + unsigned long *resp_len = (unsigned long *)data->kaddr2; + + if (data->status != BIT(SVC_STATUS_OK) || (*mbox_err) || + (*resp_len != RSU_GET_SPT_RESP_LEN)) + goto error; + + priv->spt0_address = priv->get_spt_response_buf[0]; + priv->spt0_address <<= 32; + priv->spt0_address |= priv->get_spt_response_buf[1]; + priv->spt1_address = priv->get_spt_response_buf[2]; + priv->spt1_address <<= 32; + priv->spt1_address |= priv->get_spt_response_buf[3]; + + goto complete; + +error: + dev_err(priv->client.dev, + "failed to get SPTs (status=%#x, mbox_err=%lu, resp_len=%lu)\n", + data->status, mbox_err ? *mbox_err : 0, + resp_len ? *resp_len : 0); + +complete: + stratix10_svc_free_memory(priv->chan, priv->get_spt_response_buf); + priv->get_spt_response_buf = NULL; + complete(&priv->completion); +} + /** * __rsu_send_msg_locked() - send a message to Intel service layer * @priv: pointer to rsu private data @@ -353,7 +460,7 @@ static int __rsu_send_msg_locked(struct stratix10_rsu_priv *priv, unsigned long arg, rsu_callback callback) { - struct stratix10_svc_client_msg msg; + struct stratix10_svc_client_msg msg = {0}; int ret; lockdep_assert_held(&priv->lock); @@ -365,6 +472,14 @@ static int __rsu_send_msg_locked(struct stratix10_rsu_priv *priv, if (arg) msg.arg[0] = arg; + if (command == COMMAND_MBOX_SEND_CMD) { + msg.arg[1] = 0; + msg.payload = NULL; + msg.payload_length = 0; + msg.payload_output = priv->get_spt_response_buf; + msg.payload_length_output = RSU_GET_SPT_RESP_LEN; + } + ret = stratix10_svc_send(priv->chan, &msg); if (ret < 0) goto status_done; @@ -489,6 +604,8 @@ static int rsu_send_async_msg(struct device *dev, struct stratix10_rsu_priv *pri if (status && !handle) { dev_err(dev, "Failed to send async message\n"); + if (msg.payload_output) + stratix10_svc_free_memory(priv->chan, msg.payload_output); return -ETIMEDOUT; } @@ -528,6 +645,8 @@ static int rsu_send_async_msg(struct device *dev, struct stratix10_rsu_priv *pri } status_done: + if (msg.payload_output) + stratix10_svc_free_memory(priv->chan, msg.payload_output); stratix10_svc_async_done(priv->chan, handle); return ret; } @@ -775,15 +894,30 @@ static ssize_t notify_store(struct device *dev, if (ret) return ret; - ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_NOTIFY, status, NULL); + if (priv->async) + ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_NOTIFY, status, NULL); + else + ret = rsu_send_msg(priv, COMMAND_RSU_NOTIFY, status, rsu_command_callback); if (ret) { dev_err(dev, "Error, RSU notify returned %i\n", ret); return ret; } /* to get the updated state */ - ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_STATUS, 0, - rsu_async_status_callback); + if (priv->async) { + ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_STATUS, 0, + rsu_async_status_callback); + } else { + ret = rsu_send_msg(priv, COMMAND_RSU_STATUS, 0, + rsu_status_callback); + /* + * In async mode COMMAND_RSU_RETRY is part of COMMAND_RSU_STATUS; + * issue it separately only in synchronous mode. + */ + if (!ret) + ret = rsu_send_msg(priv, COMMAND_RSU_RETRY, 0, + rsu_retry_callback); + } if (ret) { dev_err(dev, "Error, getting RSU status %i\n", ret); return ret; @@ -974,7 +1108,11 @@ static int stratix10_rsu_probe(struct platform_device *pdev) priv->dcmf_status.dcmf1 = INVALID_DCMF_STATUS; priv->dcmf_status.dcmf2 = INVALID_DCMF_STATUS; priv->dcmf_status.dcmf3 = INVALID_DCMF_STATUS; - /* spt0/1_address and status fields default to 0 from kzalloc */ + priv->max_retry = INVALID_RETRY_COUNTER; + priv->spt0_address = INVALID_SPT_ADDRESS; + priv->spt1_address = INVALID_SPT_ADDRESS; + priv->get_spt_response_buf = NULL; + priv->async = false; rsu_device_info_invalidate(priv); mutex_init(&priv->lock); @@ -989,19 +1127,39 @@ static int stratix10_rsu_probe(struct platform_device *pdev) } ret = stratix10_svc_add_async_client(priv->chan, false); - if (ret) { - dev_err(dev, "failed to add async client\n"); - goto free_channel; + if (ret < 0) { + dev_dbg(dev, "Async operations not supported, fallback to non-async mode\n"); + priv->async = false; + } else { + priv->async = true; } platform_set_drvdata(pdev, priv); /* get the initial state from firmware */ - ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_STATUS, 0, - rsu_async_status_callback); + if (priv->async) + ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_STATUS, 0, + rsu_async_status_callback); + else + ret = rsu_send_msg(priv, COMMAND_RSU_STATUS, 0, + rsu_status_callback); if (ret) { dev_err(dev, "Error, getting RSU status %i\n", ret); - goto remove_async_client; + if (priv->async) + goto remove_async_client; + goto free_channel; + } + + /* + * In async mode COMMAND_RSU_RETRY is part of COMMAND_RSU_STATUS; + * issue it separately only in synchronous mode. + */ + if (!priv->async) { + ret = rsu_send_msg(priv, COMMAND_RSU_RETRY, 0, rsu_retry_callback); + if (ret) { + dev_err(dev, "Error, getting RSU retry %i\n", ret); + goto free_channel; + } } /* get DCMF version from firmware */ @@ -1009,21 +1167,27 @@ static int stratix10_rsu_probe(struct platform_device *pdev) rsu_dcmf_version_callback); if (ret) { dev_err(dev, "Error, getting DCMF version %i\n", ret); - goto remove_async_client; + if (priv->async) + goto remove_async_client; + goto free_channel; } ret = rsu_send_msg(priv, COMMAND_RSU_DCMF_STATUS, 0, rsu_dcmf_status_callback); if (ret) { dev_err(dev, "Error, getting DCMF status %i\n", ret); - goto remove_async_client; + if (priv->async) + goto remove_async_client; + goto free_channel; } ret = rsu_send_msg(priv, COMMAND_RSU_MAX_RETRY, 0, rsu_max_retry_callback); if (ret) { dev_err(dev, "Error, getting RSU max retry %i\n", ret); - goto remove_async_client; + if (priv->async) + goto remove_async_client; + goto free_channel; } /* get QSPI device info from firmware */ @@ -1031,16 +1195,36 @@ static int stratix10_rsu_probe(struct platform_device *pdev) rsu_get_device_info_callback); if (ret) { dev_err(dev, "Error, getting QSPI Device Info %i\n", ret); - stratix10_svc_remove_async_client(priv->chan); - stratix10_svc_free_channel(priv->chan); - return ret; + if (priv->async) + goto remove_async_client; + goto free_channel; } - ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_GET_SPT_TABLE, 0, - rsu_async_get_spt_table_callback); + if (priv->async) { + ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_GET_SPT_TABLE, + 0, rsu_async_get_spt_table_callback); + } else { + priv->get_spt_response_buf = + stratix10_svc_allocate_memory(priv->chan, RSU_GET_SPT_RESP_LEN); + if (IS_ERR(priv->get_spt_response_buf)) { + ret = PTR_ERR(priv->get_spt_response_buf); + priv->get_spt_response_buf = NULL; + dev_err(dev, "failed to allocate get spt buffer\n"); + } else { + ret = rsu_send_msg(priv, COMMAND_MBOX_SEND_CMD, + RSU_GET_SPT_CMD, rsu_get_spt_callback); + } + } if (ret) { dev_err(dev, "Error, getting SPT table %i\n", ret); - goto remove_async_client; + if (priv->async) { + goto remove_async_client; + } else if (!IS_ERR_OR_NULL(priv->get_spt_response_buf)) { + stratix10_svc_free_memory(priv->chan, + priv->get_spt_response_buf); + priv->get_spt_response_buf = NULL; + } + goto free_channel; } return 0; @@ -1056,6 +1240,9 @@ static void stratix10_rsu_remove(struct platform_device *pdev) { struct stratix10_rsu_priv *priv = platform_get_drvdata(pdev); + if (priv->async) + stratix10_svc_remove_async_client(priv->chan); + stratix10_svc_free_channel(priv->chan); } From 728e9895bd648760fbac90f32a679415c44068a6 Mon Sep 17 00:00:00 2001 From: Tze Yee Ng Date: Wed, 22 Jul 2026 23:02:06 -0700 Subject: [PATCH 4/5] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device Add asynchronous Stratix 10 service layer support for hardware monitor temperature and voltage read commands in stratix10_svc_async_send() and stratix10_svc_async_prepare_response(). Register a socfpga-hwmon platform device from the service layer driver when hardware monitor support is enabled, similar to the RSU device. Signed-off-by: Nazim Amirul Signed-off-by: Tze Yee Ng Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-svc.c | 46 ++++++++++++++++++-- include/linux/firmware/intel/stratix10-smc.h | 38 ++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index de938ab2db0b..08395fe3ddaa 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -46,6 +46,7 @@ /* stratix10 service layer clients */ #define STRATIX10_RSU "stratix10-rsu" +#define SOCFPGA_HWMON "socfpga-hwmon" /* Maximum number of SDM client IDs. */ #define MAX_SDM_CLIENT_IDS 16 @@ -105,9 +106,11 @@ struct stratix10_svc_chan; /** * struct stratix10_svc - svc private data * @stratix10_svc_rsu: pointer to stratix10 RSU device + * @stratix10_svc_hwmon: pointer to stratix10 HWMON device */ struct stratix10_svc { struct platform_device *stratix10_svc_rsu; + struct platform_device *stratix10_svc_hwmon; }; /** @@ -1405,6 +1408,14 @@ int stratix10_svc_async_send(struct stratix10_svc_chan *chan, void *msg, args.a0 = INTEL_SIP_SMC_ASYNC_RSU_NOTIFY; args.a2 = p_msg->arg[0]; break; + case COMMAND_HWMON_READTEMP: + args.a0 = INTEL_SIP_SMC_ASYNC_HWMON_READTEMP; + args.a2 = p_msg->arg[0]; + break; + case COMMAND_HWMON_READVOLT: + args.a0 = INTEL_SIP_SMC_ASYNC_HWMON_READVOLT; + args.a2 = p_msg->arg[0]; + break; default: dev_err(ctrl->dev, "Invalid command ,%d\n", p_msg->command); ret = -EINVAL; @@ -1498,6 +1509,10 @@ static int stratix10_svc_async_prepare_response(struct stratix10_svc_chan *chan, */ data->kaddr1 = (void *)&handle->res; break; + case COMMAND_HWMON_READTEMP: + case COMMAND_HWMON_READVOLT: + data->kaddr1 = (void *)&handle->res.a2; + break; default: dev_alert(ctrl->dev, "Invalid command\n ,%d", p_msg->command); @@ -2089,16 +2104,38 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) if (ret) goto err_put_device; + if (IS_ENABLED(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON)) { + svc->stratix10_svc_hwmon = + platform_device_alloc(SOCFPGA_HWMON, 0); + if (!svc->stratix10_svc_hwmon) { + dev_err(dev, "failed to allocate %s device\n", + SOCFPGA_HWMON); + } else { + svc->stratix10_svc_hwmon->dev.parent = dev; + + ret = platform_device_add(svc->stratix10_svc_hwmon); + if (ret) { + dev_err(dev, "failed to add %s device: %d\n", + SOCFPGA_HWMON, ret); + platform_device_put(svc->stratix10_svc_hwmon); + svc->stratix10_svc_hwmon = NULL; + } + } + } + ret = of_platform_default_populate(dev_of_node(dev), NULL, dev); if (ret) - goto err_unregister_rsu_dev; + goto err_unregister_clients; pr_info("Intel Service Layer Driver Initialized\n"); return 0; -err_unregister_rsu_dev: - platform_device_unregister(svc->stratix10_svc_rsu); +err_unregister_clients: + if (svc->stratix10_svc_hwmon) + platform_device_unregister(svc->stratix10_svc_hwmon); + if (svc->stratix10_svc_rsu) + platform_device_unregister(svc->stratix10_svc_rsu); goto err_free_fifos; err_put_device: platform_device_put(svc->stratix10_svc_rsu); @@ -2122,6 +2159,9 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev) struct stratix10_svc_controller *ctrl = platform_get_drvdata(pdev); struct stratix10_svc *svc = ctrl->svc; + if (svc->stratix10_svc_hwmon) + platform_device_unregister(svc->stratix10_svc_hwmon); + stratix10_svc_async_exit(ctrl); of_platform_depopulate(ctrl->dev); diff --git a/include/linux/firmware/intel/stratix10-smc.h b/include/linux/firmware/intel/stratix10-smc.h index 6e042943b6ce..ce1d2c7203b8 100644 --- a/include/linux/firmware/intel/stratix10-smc.h +++ b/include/linux/firmware/intel/stratix10-smc.h @@ -718,6 +718,44 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE) #define INTEL_SIP_SMC_ASYNC_POLL \ INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_POLL) +/** + * Request INTEL_SIP_SMC_ASYNC_HWMON_READTEMP + * Async call to request temperature + * + * Call register usage: + * a0 INTEL_SIP_SMC_ASYNC_HWMON_READTEMP + * a1 transaction job id + * a2 Temperature Channel + * a3-a17 not used + * + * Return status + * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_STATUS_REJECTED + * or INTEL_SIP_SMC_STATUS_BUSY + * a1-a17 not used + */ +#define INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READTEMP 0xE8 +#define INTEL_SIP_SMC_ASYNC_HWMON_READTEMP \ + INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READTEMP) + +/** + * Request INTEL_SIP_SMC_ASYNC_HWMON_READVOLT + * Async call to request voltage + * + * Call register usage: + * a0 INTEL_SIP_SMC_ASYNC_HWMON_READVOLT + * a1 transaction job id + * a2 Voltage Channel + * a3-a17 not used + * + * Return status + * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_STATUS_REJECTED + * or INTEL_SIP_SMC_STATUS_BUSY + * a1-a17 not used + */ +#define INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READVOLT 0xE9 +#define INTEL_SIP_SMC_ASYNC_HWMON_READVOLT \ + INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READVOLT) + /** * Request INTEL_SIP_SMC_ASYNC_RSU_GET_SPT * Async call to get RSU SPT from SDM. From 758331ab5f9355352ccac3d7a83d3586bec19f9e Mon Sep 17 00:00:00 2001 From: Tze Yee Ng Date: Wed, 22 Jul 2026 23:02:07 -0700 Subject: [PATCH 5/5] hwmon: add Altera SoC FPGA hardware monitoring driver Add a hardware monitor driver for Altera SoC FPGA devices using the Stratix 10 service layer. Sensor channels are selected based on the service layer compatible string. Signed-off-by: Nazim Amirul Signed-off-by: Tze Yee Ng Acked-by: Guenter Roeck Link: https://lore.kernel.org/all/a79aee37be7067691fd02a3a9cfd134c995e5a95.1784785709.git.tze.yee.ng@altera.com/#r Signed-off-by: Dinh Nguyen --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/socfpga-hwmon.rst | 34 ++ MAINTAINERS | 8 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/socfpga-hwmon.c | 579 ++++++++++++++++++++++++++ 6 files changed, 633 insertions(+) create mode 100644 Documentation/hwmon/socfpga-hwmon.rst create mode 100644 drivers/hwmon/socfpga-hwmon.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 29130df44d12..dfab6fec09bb 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -251,6 +251,7 @@ Hardware Monitoring Kernel Drivers smsc47b397 smsc47m192 smsc47m1 + socfpga-hwmon sparx5-temp spd5118 stpddc60 diff --git a/Documentation/hwmon/socfpga-hwmon.rst b/Documentation/hwmon/socfpga-hwmon.rst new file mode 100644 index 000000000000..e5da42556a62 --- /dev/null +++ b/Documentation/hwmon/socfpga-hwmon.rst @@ -0,0 +1,34 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver socfpga-hwmon +============================= + +Supported chips: + + * Altera Stratix 10 SoC FPGA + * Altera Agilex SoC FPGA + +Authors: + - Nazim Amirul + - Tze Yee Ng + +Description +----------- + +This driver supports hardware monitoring for Altera SoC +FPGA devices through the Secure Device Manager and Stratix 10 service layer. + +The following sensor types are supported: + + * temperature + * voltage + +Usage Notes +----------- + +The stratix10-svc driver registers a socfpga-hwmon platform device when +hardware monitor support is enabled. Sensor channels are selected in the +driver based on the service layer compatible string: + + * intel,stratix10-svc + * intel,agilex-svc diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..2b0ee6d5c6ec 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -965,6 +965,14 @@ L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-altera.c +ALTERA SoC FPGA HWMON DRIVER +M: Nazim Amirul +M: Tze Yee Ng +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/socfpga-hwmon.rst +F: drivers/hwmon/socfpga-hwmon.c + ALTERA TRIPLE SPEED ETHERNET DRIVER M: Boon Khai Ng L: netdev@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5c2d3ff5fce8..54ce630c7de6 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2157,6 +2157,16 @@ config SENSORS_SMSC47M192 This driver can also be built as a module. If so, the module will be called smsc47m192. +config SENSORS_ALTERA_SOCFPGA_HWMON + tristate "Altera SoC FPGA hardware monitoring features" + depends on INTEL_STRATIX10_SERVICE + help + If you say yes here you get support for the temperature and + voltage sensors of Altera SoC FPGA devices. + + This driver can also be built as a module. If so, the module + will be called socfpga-hwmon. + config SENSORS_SMSC47B397 tristate "SMSC LPC47B397-NC" depends on HAS_IOPORT diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 63effc0ab8d1..aeedee80e1f0 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -221,6 +221,7 @@ obj-$(CONFIG_SENSORS_SMPRO) += smpro-hwmon.o obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o +obj-$(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON) += socfpga-hwmon.o obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o obj-$(CONFIG_SENSORS_SPD5118) += spd5118.o obj-$(CONFIG_SENSORS_STTS751) += stts751.o diff --git a/drivers/hwmon/socfpga-hwmon.c b/drivers/hwmon/socfpga-hwmon.c new file mode 100644 index 000000000000..5b43274d0aa2 --- /dev/null +++ b/drivers/hwmon/socfpga-hwmon.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Altera SoC FPGA hardware monitoring driver + * + * Copyright (c) 2026 Altera Corporation + * + * Authors: + * Nazim Amirul + * Tze Yee Ng + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HWMON_TIMEOUT msecs_to_jiffies(SVC_HWMON_REQUEST_TIMEOUT_MS) +#define HWMON_RETRY_SLEEP_US 1000U +#define HWMON_ASYNC_MSG_RETRY 3U +#define SOCFPGA_HWMON_MAXSENSORS 16 +#define SOCFPGA_HWMON_CHANNEL_MASK GENMASK(15, 0) +#define SOCFPGA_HWMON_PAGE_SHIFT 16 +#define SOCFPGA_HWMON_CHAN(page, channel) \ + (((page) << SOCFPGA_HWMON_PAGE_SHIFT) | \ + ((channel) & SOCFPGA_HWMON_CHANNEL_MASK)) +#define SOCFPGA_HWMON_ATTR_VISIBLE 0444 +/* Temperature from SDM is signed Q8.8 degrees Celsius (8 fractional bits). */ +#define SOCFPGA_HWMON_TEMP_FRAC_BITS 8 +#define SOCFPGA_HWMON_TEMP_FRAC_DIV BIT(SOCFPGA_HWMON_TEMP_FRAC_BITS) +#define SOCFPGA_HWMON_TEMP_MDEG_SCALE 1000 +/* Voltage from SDM is unsigned Q16 volts (16 fractional bits). */ +#define SOCFPGA_HWMON_VOLT_FRAC_BITS 16 +#define SOCFPGA_HWMON_VOLT_FRAC_DIV BIT(SOCFPGA_HWMON_VOLT_FRAC_BITS) +#define SOCFPGA_HWMON_VOLT_MV_SCALE 1000 + +#define ETEMP_INACTIVE 0x80000000U +#define ETEMP_TOO_OLD 0x80000001U +#define ETEMP_NOT_PRESENT 0x80000002U +#define ETEMP_TIMEOUT 0x80000003U +#define ETEMP_CORRUPT 0x80000004U +#define ETEMP_BUSY 0x80000005U +#define ETEMP_NOT_INITIALIZED 0x800000FFU + +struct socfpga_hwmon_channel { + u32 reg; + const char *label; +}; + +struct socfpga_hwmon_board_data { + const struct socfpga_hwmon_channel *temp; + unsigned int num_temp; + const struct socfpga_hwmon_channel *volt; + unsigned int num_volt; +}; + +struct socfpga_hwmon_priv { + struct stratix10_svc_chan *chan; + struct stratix10_svc_client client; + struct completion completion; + struct mutex lock; /* protect SVC calls */ + bool async; + int last_err; /* sync-mode SVC result; 0 on success */ + u32 temperature; + u32 voltage; + int temperature_channels; + int voltage_channels; + const char *temp_chan_names[SOCFPGA_HWMON_MAXSENSORS]; + const char *volt_chan_names[SOCFPGA_HWMON_MAXSENSORS]; + u32 temp_chan[SOCFPGA_HWMON_MAXSENSORS]; + u32 volt_chan[SOCFPGA_HWMON_MAXSENSORS]; +}; + +static umode_t socfpga_hwmon_is_visible(const void *dev, + enum hwmon_sensor_types type, + u32 attr, int chan) +{ + const struct socfpga_hwmon_priv *priv = dev; + + switch (type) { + case hwmon_temp: + if (chan < priv->temperature_channels) + return SOCFPGA_HWMON_ATTR_VISIBLE; + return 0; + case hwmon_in: + if (chan < priv->voltage_channels) + return SOCFPGA_HWMON_ATTR_VISIBLE; + return 0; + default: + return 0; + } +} + +static void socfpga_hwmon_readtemp_cb(struct stratix10_svc_client *client, + struct stratix10_svc_cb_data *data) +{ + struct socfpga_hwmon_priv *priv = client->priv; + + priv->last_err = -EIO; + if (data->status == BIT(SVC_STATUS_OK)) { + priv->last_err = 0; + priv->temperature = (u32)*(unsigned long *)data->kaddr1; + } else if (data->kaddr1) { + dev_err(client->dev, "%s failed with status 0x%x, value 0x%lx\n", + __func__, data->status, + *(unsigned long *)data->kaddr1); + } else { + dev_err(client->dev, "%s failed with status 0x%x\n", + __func__, data->status); + } + + complete(&priv->completion); +} + +static void socfpga_hwmon_readvolt_cb(struct stratix10_svc_client *client, + struct stratix10_svc_cb_data *data) +{ + struct socfpga_hwmon_priv *priv = client->priv; + + priv->last_err = -EIO; + if (data->status == BIT(SVC_STATUS_OK)) { + priv->last_err = 0; + priv->voltage = (u32)*(unsigned long *)data->kaddr1; + } else if (data->kaddr1) { + dev_err(client->dev, "%s failed with status 0x%x, value 0x%lx\n", + __func__, data->status, + *(unsigned long *)data->kaddr1); + } else { + dev_err(client->dev, "%s failed with status 0x%x\n", + __func__, data->status); + } + + complete(&priv->completion); +} + +static int socfpga_hwmon_parse_temp(long *val, u32 temperature) +{ + switch (temperature) { + case ETEMP_INACTIVE: + case ETEMP_NOT_PRESENT: + case ETEMP_CORRUPT: + case ETEMP_NOT_INITIALIZED: + return -EOPNOTSUPP; + case ETEMP_TIMEOUT: + case ETEMP_BUSY: + case ETEMP_TOO_OLD: + return -EAGAIN; + default: + /* SDM returns a 16-bit signed Q8.8 value in the low 16 bits. */ + *val = (long)(s16)(temperature & SOCFPGA_HWMON_CHANNEL_MASK) * + SOCFPGA_HWMON_TEMP_MDEG_SCALE / SOCFPGA_HWMON_TEMP_FRAC_DIV; + return 0; + } +} + +static int socfpga_hwmon_encode_temp_arg(u32 reg, u64 *arg) +{ + u32 page = (reg >> SOCFPGA_HWMON_PAGE_SHIFT) & SOCFPGA_HWMON_CHANNEL_MASK; + u32 channel = reg & SOCFPGA_HWMON_CHANNEL_MASK; + + if (channel >= SOCFPGA_HWMON_MAXSENSORS) + return -EINVAL; + + *arg = (1ULL << channel) | ((u64)page << SOCFPGA_HWMON_PAGE_SHIFT); + return 0; +} + +static int socfpga_hwmon_encode_volt_arg(u32 reg, u64 *arg) +{ + u32 channel = reg & SOCFPGA_HWMON_CHANNEL_MASK; + + if (channel >= SOCFPGA_HWMON_MAXSENSORS) + return -EINVAL; + + *arg = 1ULL << channel; + return 0; +} + +static int socfpga_hwmon_async_read(struct device *dev, + enum hwmon_sensor_types type, + struct stratix10_svc_client_msg *msg) +{ + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev); + struct stratix10_svc_cb_data data = {}; + unsigned long deadline = jiffies + HWMON_TIMEOUT; + void *handle = NULL; + int status, index, ret; + + for (index = 0; index < HWMON_ASYNC_MSG_RETRY; index++) { + status = stratix10_svc_async_send(priv->chan, msg, &handle, + NULL, NULL); + if (status == 0) + break; + dev_warn(dev, "Failed to send async message: %d\n", status); + usleep_range(HWMON_RETRY_SLEEP_US, HWMON_RETRY_SLEEP_US * 2); + } + + if (status && !handle) { + dev_err(dev, "Failed to send async message after %u retries: %d\n", + HWMON_ASYNC_MSG_RETRY, status); + return status; + } + + ret = -ETIMEDOUT; + while (!time_after(jiffies, deadline)) { + status = stratix10_svc_async_poll(priv->chan, handle, &data); + if (status == -EAGAIN) { + /* still in progress */ + } else if (status < 0) { + ret = status; + break; + } else if (status == 0) { + ret = 0; + break; + } + usleep_range(HWMON_RETRY_SLEEP_US, HWMON_RETRY_SLEEP_US * 2); + } + + if (ret) { + dev_err(dev, "Failed to get async response\n"); + goto done; + } + + if (data.status) { + dev_err(dev, "%s returned 0x%x from SDM\n", __func__, + data.status); + ret = -EFAULT; + goto done; + } + + if (type == hwmon_temp) + priv->temperature = (u32)*(unsigned long *)data.kaddr1; + else + priv->voltage = (u32)*(unsigned long *)data.kaddr1; + + ret = 0; + +done: + stratix10_svc_async_done(priv->chan, handle); + return ret; +} + +static int socfpga_hwmon_sync_read(struct device *dev, + enum hwmon_sensor_types type, + struct stratix10_svc_client_msg *msg) +{ + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev); + int ret; + + reinit_completion(&priv->completion); + + if (type == hwmon_temp) + priv->client.receive_cb = socfpga_hwmon_readtemp_cb; + else + priv->client.receive_cb = socfpga_hwmon_readvolt_cb; + + ret = stratix10_svc_send(priv->chan, msg); + if (ret < 0) + goto status_done; + + ret = wait_for_completion_timeout(&priv->completion, HWMON_TIMEOUT); + if (!ret) { + dev_err(priv->client.dev, "timeout waiting for SMC call\n"); + ret = -ETIMEDOUT; + goto status_done; + } + + ret = priv->last_err; + +status_done: + stratix10_svc_done(priv->chan); + return ret; +} + +static int socfpga_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int chan, long *val) +{ + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev); + struct stratix10_svc_client_msg msg = {0}; + int ret; + + if (chan >= SOCFPGA_HWMON_MAXSENSORS) + return -EOPNOTSUPP; + + switch (type) { + case hwmon_temp: + ret = socfpga_hwmon_encode_temp_arg(priv->temp_chan[chan], + &msg.arg[0]); + if (ret) + return ret; + msg.command = COMMAND_HWMON_READTEMP; + break; + case hwmon_in: + ret = socfpga_hwmon_encode_volt_arg(priv->volt_chan[chan], + &msg.arg[0]); + if (ret) + return ret; + msg.command = COMMAND_HWMON_READVOLT; + break; + default: + return -EOPNOTSUPP; + } + + guard(mutex)(&priv->lock); + if (priv->async) + ret = socfpga_hwmon_async_read(dev, type, &msg); + else + ret = socfpga_hwmon_sync_read(dev, type, &msg); + if (ret) + return ret; + + if (type == hwmon_temp) + ret = socfpga_hwmon_parse_temp(val, priv->temperature); + else + /* SDM returns Q16 volts; convert to hwmon millivolts. */ + *val = (long)priv->voltage * SOCFPGA_HWMON_VOLT_MV_SCALE / + SOCFPGA_HWMON_VOLT_FRAC_DIV; + return ret; +} + +static int socfpga_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int chan, const char **str) +{ + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev); + + switch (type) { + case hwmon_in: + *str = priv->volt_chan_names[chan]; + return 0; + case hwmon_temp: + *str = priv->temp_chan_names[chan]; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops socfpga_hwmon_ops = { + .is_visible = socfpga_hwmon_is_visible, + .read = socfpga_hwmon_read, + .read_string = socfpga_hwmon_read_string, +}; + +static const struct hwmon_channel_info *socfpga_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + NULL +}; + +static const struct hwmon_chip_info socfpga_hwmon_chip_info = { + .ops = &socfpga_hwmon_ops, + .info = socfpga_hwmon_info, +}; + +static const struct socfpga_hwmon_channel s10_hwmon_volt_channels[] = { + { SOCFPGA_HWMON_CHAN(0, 2), "0.8V VCC" }, + { SOCFPGA_HWMON_CHAN(0, 3), "1.8V VCCIO_SDM" }, + { SOCFPGA_HWMON_CHAN(0, 6), "0.9V VCCERAM" }, +}; + +static const struct socfpga_hwmon_channel s10_hwmon_temp_channels[] = { + { SOCFPGA_HWMON_CHAN(0, 0), "Main Die SDM" }, +}; + +static const struct socfpga_hwmon_board_data s10_hwmon_board = { + .temp = s10_hwmon_temp_channels, + .num_temp = ARRAY_SIZE(s10_hwmon_temp_channels), + .volt = s10_hwmon_volt_channels, + .num_volt = ARRAY_SIZE(s10_hwmon_volt_channels), +}; + +static const struct socfpga_hwmon_channel agilex_hwmon_volt_channels[] = { + { SOCFPGA_HWMON_CHAN(0, 2), "0.8V VCC" }, + { SOCFPGA_HWMON_CHAN(0, 3), "1.8V VCCIO_SDM" }, + { SOCFPGA_HWMON_CHAN(0, 4), "1.8V VCCPT" }, + { SOCFPGA_HWMON_CHAN(0, 5), "1.2V VCCCRCORE" }, + { SOCFPGA_HWMON_CHAN(0, 6), "0.9V VCCH" }, + { SOCFPGA_HWMON_CHAN(0, 7), "0.8V VCCL" }, +}; + +static const struct socfpga_hwmon_channel agilex_hwmon_temp_channels[] = { + { SOCFPGA_HWMON_CHAN(0, 0), "Main Die SDM" }, + { SOCFPGA_HWMON_CHAN(1, 0), "Main Die corner bottom left max" }, + { SOCFPGA_HWMON_CHAN(2, 0), "Main Die corner top left max" }, + { SOCFPGA_HWMON_CHAN(3, 0), "Main Die corner bottom right max" }, + { SOCFPGA_HWMON_CHAN(4, 0), "Main Die corner top right max" }, +}; + +static const struct socfpga_hwmon_board_data agilex_hwmon_board = { + .temp = agilex_hwmon_temp_channels, + .num_temp = ARRAY_SIZE(agilex_hwmon_temp_channels), + .volt = agilex_hwmon_volt_channels, + .num_volt = ARRAY_SIZE(agilex_hwmon_volt_channels), +}; + +static const struct socfpga_hwmon_board_data * +socfpga_hwmon_get_board(struct device *dev) +{ + struct device_node *np = dev->of_node; + + if (!np) + return NULL; + + if (of_device_is_compatible(np, "intel,stratix10-svc")) + return &s10_hwmon_board; + if (of_device_is_compatible(np, "intel,agilex-svc")) + return &agilex_hwmon_board; + + return NULL; +} + +static int socfpga_hwmon_init_channels(struct device *dev, + const struct socfpga_hwmon_board_data *board, + struct socfpga_hwmon_priv *priv) +{ + unsigned int i; + + if (board->num_temp > SOCFPGA_HWMON_MAXSENSORS || + board->num_volt > SOCFPGA_HWMON_MAXSENSORS) + return -EINVAL; + + for (i = 0; i < board->num_temp; i++) { + priv->temp_chan_names[i] = board->temp[i].label; + priv->temp_chan[i] = board->temp[i].reg; + } + priv->temperature_channels = board->num_temp; + + for (i = 0; i < board->num_volt; i++) { + priv->volt_chan_names[i] = board->volt[i].label; + priv->volt_chan[i] = board->volt[i].reg; + } + priv->voltage_channels = board->num_volt; + + return 0; +} + +static void socfpga_hwmon_release_svc(void *data) +{ + struct socfpga_hwmon_priv *priv = data; + + if (priv->async) + stratix10_svc_remove_async_client(priv->chan); + stratix10_svc_free_channel(priv->chan); +} + +static int socfpga_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *parent = dev->parent; + const struct socfpga_hwmon_board_data *board; + struct socfpga_hwmon_priv *priv; + struct device *hwmon_dev; + int ret; + + if (!parent || !parent->of_node) { + dev_err(dev, "missing parent device node\n"); + return -ENODEV; + } + + board = socfpga_hwmon_get_board(parent); + if (!board) { + dev_err(dev, "unsupported service layer compatible\n"); + return -ENODEV; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client.dev = dev; + priv->client.priv = priv; + init_completion(&priv->completion); + mutex_init(&priv->lock); + + ret = socfpga_hwmon_init_channels(dev, board, priv); + if (ret) + return ret; + + priv->chan = stratix10_svc_request_channel_byname(&priv->client, + SVC_CLIENT_HWMON); + if (IS_ERR(priv->chan)) { + ret = PTR_ERR(priv->chan); + if (ret == -EPROBE_DEFER) + dev_dbg(dev, "service channel %s not ready, deferring probe\n", + SVC_CLIENT_HWMON); + else + dev_err(dev, "couldn't get service channel %s: %d\n", + SVC_CLIENT_HWMON, ret); + return ret; + } + + ret = stratix10_svc_add_async_client(priv->chan, false); + switch (ret) { + case 0: + priv->async = true; + break; + case -EINVAL: + case -EOPNOTSUPP: + /* + * stratix10_svc_add_async_client() returns -EINVAL when the + * async controller is not initialized; fall back to sync mode. + */ + dev_dbg(dev, "async operations not supported, using sync mode\n"); + priv->async = false; + break; + default: + dev_err(dev, "failed to add async client: %d\n", ret); + stratix10_svc_free_channel(priv->chan); + return ret; + } + + ret = devm_add_action_or_reset(dev, socfpga_hwmon_release_svc, priv); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, "socfpga_hwmon", + priv, + &socfpga_hwmon_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + platform_set_drvdata(pdev, priv); + return 0; +} + +static struct platform_driver socfpga_hwmon_driver = { + .probe = socfpga_hwmon_probe, + .driver = { + .name = "socfpga-hwmon", + }, +}; +module_platform_driver(socfpga_hwmon_driver); + +MODULE_AUTHOR("Nazim Amirul "); +MODULE_AUTHOR("Tze Yee Ng "); +MODULE_DESCRIPTION("Altera SoC FPGA hardware monitoring driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:socfpga-hwmon");