mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 12:13:39 -04:00
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:
@@ -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.
|
||||
|
||||
@@ -251,6 +251,7 @@ Hardware Monitoring Kernel Drivers
|
||||
smsc47b397
|
||||
smsc47m192
|
||||
smsc47m1
|
||||
socfpga-hwmon
|
||||
sparx5-temp
|
||||
spd5118
|
||||
stpddc60
|
||||
|
||||
34
Documentation/hwmon/socfpga-hwmon.rst
Normal file
34
Documentation/hwmon/socfpga-hwmon.rst
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
579
drivers/hwmon/socfpga-hwmon.c
Normal file
579
drivers/hwmon/socfpga-hwmon.c
Normal 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");
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user