Merge tag 'svc_updates_for_v7.3' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux into char-misc-next

SoCFPGA firmware updates for v7.3
- Document stratix10-rsu for QSPI size and erase size
- Add method to retrieve device info table using RSU
- Add support for hardware monitoring using service driver

Resolves merge conflicts in:
	drivers/firmware/stratix10-svc.c

Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

* tag 'svc_updates_for_v7.3' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux:
  hwmon: add Altera SoC FPGA hardware monitoring driver
  firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device
  firmware: stratix10-rsu: Add synchronous fallback for async SVC operations
  firmware: stratix10-rsu: Add flash device info retrieval via SMC
  Documentation: ABI: add stratix10-rsu QSPI size and erase_size sysfs
This commit is contained in:
Greg Kroah-Hartman
2026-07-31 14:14:51 +02:00
11 changed files with 1290 additions and 36 deletions

View File

@@ -172,3 +172,61 @@ Contact: Richard Gong <richard.gong@linux.intel.com>
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 <tze.yee.ng@altera.com>
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 <tze.yee.ng@altera.com>
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 <tze.yee.ng@altera.com>
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 <tze.yee.ng@altera.com>
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 <tze.yee.ng@altera.com>
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 <tze.yee.ng@altera.com>
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 <tze.yee.ng@altera.com>
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 <tze.yee.ng@altera.com>
Description:
(RO) Like erase_size0 for device 3.

View File

@@ -251,6 +251,7 @@ Hardware Monitoring Kernel Drivers
smsc47b397
smsc47m192
smsc47m1
socfpga-hwmon
sparx5-temp
spd5118
stpddc60

View File

@@ -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 <muhammad.nazim.amirul.nazle.asmade@altera.com>
- Tze Yee Ng <tze.yee.ng@altera.com>
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

View File

@@ -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 <muhammad.nazim.amirul.nazle.asmade@altera.com>
M: Tze Yee Ng <tze.yee.ng@altera.com>
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 <boon.khai.ng@altera.com>
L: netdev@vger.kernel.org

View File

@@ -7,17 +7,28 @@
#include <linux/arm-smccc.h>
#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/firmware/intel/stratix10-svc-client.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/firmware/intel/stratix10-svc-client.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/delay.h>
#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);
}

View File

@@ -20,6 +20,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/firmware/intel/stratix10-smc.h>
#include <linux/firmware/intel/stratix10-svc-client.h>
#include <linux/types.h>
@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,579 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Altera SoC FPGA hardware monitoring driver
*
* Copyright (c) 2026 Altera Corporation
*
* Authors:
* Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
* Tze Yee Ng <tze.yee.ng@altera.com>
*/
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/firmware/intel/stratix10-svc-client.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#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 <muhammad.nazim.amirul.nazle.asmade@altera.com>");
MODULE_AUTHOR("Tze Yee Ng <tze.yee.ng@altera.com>");
MODULE_DESCRIPTION("Altera SoC FPGA hardware monitoring driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:socfpga-hwmon");

View File

@@ -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.

View File

@@ -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
*/