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. 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 8c01afd563d6..c4f6a6c32384 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/firmware/stratix10-rsu.c b/drivers/firmware/stratix10-rsu.c index daddb5224794..b360f752d191 100644 --- a/drivers/firmware/stratix10-rsu.c +++ b/drivers/firmware/stratix10-rsu.c @@ -7,17 +7,28 @@ #include #include #include +#include +#include +#include #include #include #include #include #include -#include #include #include -#include -#define RSU_ERASE_SIZE_MASK GENMASK_ULL(63, 32) +#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). + */ +#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 +44,32 @@ #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_CMD 0x5A #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); /** @@ -46,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 @@ -60,16 +93,20 @@ 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 * @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; @@ -93,16 +130,73 @@ struct stratix10_rsu_priv { unsigned int dcmf3; } dcmf_status; + struct flash_device_info device_info[4]; + unsigned int retry_counter; unsigned int max_retry; unsigned long spt0_address; unsigned long spt1_address; + + unsigned int *get_spt_response_buf; }; +/** + * 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); +/** + * 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 @@ -147,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 @@ -229,8 +349,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 @@ -243,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 @@ -259,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); @@ -271,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; @@ -395,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; } @@ -434,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; } @@ -681,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; @@ -698,6 +926,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 +1039,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 +1069,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, @@ -795,7 +1108,12 @@ 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); init_completion(&priv->completion); @@ -809,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 */ @@ -829,28 +1167,64 @@ 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; } - ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_GET_SPT_TABLE, 0, - rsu_async_get_spt_table_callback); + /* 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); + if (priv->async) + goto remove_async_client; + goto free_channel; + } + + 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; @@ -866,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); } diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index 5e20057ee344..f8c2da207cb4 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -45,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 @@ -104,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; }; /** @@ -445,13 +449,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 +519,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 +538,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 +559,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 +748,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 +771,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 +805,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 +854,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 +1079,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 @@ -1329,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; @@ -1422,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); @@ -2016,16 +2107,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); @@ -2050,6 +2163,8 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev) struct stratix10_svc *svc = ctrl->svc; platform_device_unregister(svc->stratix10_svc_rsu); + if (svc->stratix10_svc_hwmon) + platform_device_unregister(svc->stratix10_svc_hwmon); stratix10_svc_async_exit(ctrl); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2b4342abc0a7..dcdb386b0535 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2160,6 +2160,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"); diff --git a/include/linux/firmware/intel/stratix10-smc.h b/include/linux/firmware/intel/stratix10-smc.h index 9224974fffc4..366309260121 100644 --- a/include/linux/firmware/intel/stratix10-smc.h +++ b/include/linux/firmware/intel/stratix10-smc.h @@ -433,6 +433,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 @@ -497,7 +520,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) /** @@ -701,6 +724,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. 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 */