Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0-only
config AMD_SBRMI_I2C
tristate "AMD side band RMI support"
depends on I3C_OR_I2C
depends on ARM || ARM64 || COMPILE_TEST
select REGMAP_I2C
select REGMAP_I3C if I3C
help
Side band RMI over I2C/I3C support for AMD out of band management.
This driver is intended to run on the BMC, not the managed node.
This driver can also be built as a module. If so, the module will
be called sbrmi-i2c.
config AMD_SBRMI_HWMON
bool "SBRMI hardware monitoring"
depends on AMD_SBRMI_I2C && HWMON
depends on !(AMD_SBRMI_I2C=y && HWMON=m)
help
This provides support for RMI device hardware monitoring. If enabled,
a hardware monitoring device will be created for each socket in
the system.
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
sbrmi-i2c-objs += rmi-i2c.o rmi-core.o
sbrmi-i2c-$(CONFIG_AMD_SBRMI_HWMON) += rmi-hwmon.o
obj-$(CONFIG_AMD_SBRMI_I2C) += sbrmi-i2c.o
+594
View File
@@ -0,0 +1,594 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* sbrmi-core.c - file defining SB-RMI protocols compliant
* AMD SoC device.
*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include "rmi-core.h"
/* Mask for Status Register bit[1] */
#define SW_ALERT_MASK 0x2
/* Mask to check H/W Alert status bit */
#define HW_ALERT_MASK 0x80
/* Software Interrupt for triggering */
#define START_CMD 0x80
#define TRIGGER_MAILBOX 0x01
/* Default message lengths as per APML command protocol */
/* CPUID */
#define CPUID_RD_DATA_LEN 0x8
#define CPUID_WR_DATA_LEN 0x8
#define CPUID_WR_DATA_LEN_EXT 0x9
#define CPUID_RD_REG_LEN 0xa
#define CPUID_WR_REG_LEN 0x9
#define CPUID_WR_REG_LEN_EXT 0xa
/* MSR */
#define MSR_RD_REG_LEN 0xa
#define MSR_WR_REG_LEN 0x8
#define MSR_WR_REG_LEN_EXT 0x9
#define MSR_RD_DATA_LEN 0x8
#define MSR_WR_DATA_LEN 0x7
#define MSR_WR_DATA_LEN_EXT 0x8
/* CPUID MSR Command Ids */
#define CPUID_MCA_CMD 0x73
#define RD_CPUID_CMD 0x91
#define RD_MCA_CMD 0x86
/* CPUID MCAMSR mask & index */
#define CPUID_MCA_THRD_INDEX 32
#define CPUID_MCA_FUNC_MASK GENMASK(31, 0)
#define CPUID_EXT_FUNC_INDEX 48
/* input for bulk write to CPUID protocol */
struct cpu_msr_indata {
u8 wr_len; /* const value */
u8 rd_len; /* const value */
u8 proto_cmd; /* const value */
u8 thread; /* thread number */
union {
u8 reg_offset[4]; /* input value */
u32 value;
} __packed;
u8 ext; /* extended function */
};
/* input for bulk write to CPUID protocol for REV 0x21 */
struct cpu_msr_indata_ext {
u8 wr_len; /* const value */
u8 rd_len; /* const value */
u8 proto_cmd; /* const value */
u8 thread_lo; /* thread number low */
u8 thread_hi; /* thread number high */
union {
u8 reg_offset[4]; /* input value */
u32 value;
} __packed;
u8 ext; /* extended function */
};
/* output for bulk read from CPUID protocol */
struct cpu_msr_outdata {
u8 num_bytes; /* number of bytes return */
u8 status; /* Protocol status code */
union {
u64 value;
u8 reg_data[8];
} __packed;
};
static inline void prepare_cpuid_input_message(struct cpu_msr_indata *input,
u8 thread_id, u32 func,
u8 ext_func)
{
input->rd_len = CPUID_RD_DATA_LEN;
input->wr_len = CPUID_WR_DATA_LEN;
input->proto_cmd = RD_CPUID_CMD;
input->thread = thread_id << 1;
input->value = func;
input->ext = ext_func;
}
static inline void prepare_cpuid_input_message_ext(struct cpu_msr_indata_ext *input,
u16 thread_id, u32 func,
u8 ext_func)
{
input->rd_len = CPUID_RD_DATA_LEN;
input->wr_len = CPUID_WR_DATA_LEN_EXT;
input->proto_cmd = RD_CPUID_CMD;
input->thread_lo = (thread_id & 0xFF) << 1;
input->thread_hi = thread_id >> 8;
input->value = func;
input->ext = ext_func;
}
static inline void prepare_mca_msr_input_message(struct cpu_msr_indata *input,
u8 thread_id, u32 data_in)
{
input->rd_len = MSR_RD_DATA_LEN;
input->wr_len = MSR_WR_DATA_LEN;
input->proto_cmd = RD_MCA_CMD;
input->thread = thread_id << 1;
input->value = data_in;
}
static inline void prepare_mca_msr_input_message_ext(struct cpu_msr_indata_ext *input,
u16 thread_id, u32 data_in)
{
input->rd_len = MSR_RD_DATA_LEN;
input->wr_len = MSR_WR_DATA_LEN_EXT;
input->proto_cmd = RD_MCA_CMD;
input->thread_lo = (thread_id & 0xFF) << 1;
input->thread_hi = thread_id >> 8;
input->value = data_in;
}
static int sbrmi_get_rev(struct sbrmi_data *data)
{
unsigned int rev;
u16 offset = SBRMI_REV;
int ret;
ret = regmap_read(data->regmap, offset, &rev);
if (ret < 0)
return ret;
data->rev = rev;
return 0;
}
static int rmi_cpuid_input(struct sbrmi_data *data, struct apml_cpuid_msg *msg,
u16 thread)
{
struct cpu_msr_indata input = {0};
int val = 0, ret;
/* Thread > 127, Thread128 CS register, 1'b1 needs to be set to 1 */
if (thread > 127) {
thread -= 128;
val = 1;
}
ret = regmap_write(data->regmap, SBRMI_THREAD128CS, val);
if (ret < 0)
return ret;
prepare_cpuid_input_message(&input, thread,
msg->cpu_in_out & CPUID_MCA_FUNC_MASK,
msg->cpu_in_out >> CPUID_EXT_FUNC_INDEX);
return regmap_bulk_write(data->regmap, CPUID_MCA_CMD,
&input, CPUID_WR_REG_LEN);
}
static int rmi_cpuid_input_ext(struct sbrmi_data *data, struct apml_cpuid_msg *msg,
u16 thread)
{
struct cpu_msr_indata_ext input = {0};
prepare_cpuid_input_message_ext(&input, thread,
msg->cpu_in_out & CPUID_MCA_FUNC_MASK,
msg->cpu_in_out >> CPUID_EXT_FUNC_INDEX);
return regmap_bulk_write(data->regmap, CPUID_MCA_CMD,
&input, CPUID_WR_REG_LEN_EXT);
}
/* Read CPUID function protocol */
static int rmi_cpuid_read(struct sbrmi_data *data,
struct apml_cpuid_msg *msg)
{
struct cpu_msr_outdata output = {0};
int ret, hw_status;
u16 thread;
mutex_lock(&data->lock);
/* cache the rev value to identify if protocol is supported or not */
if (!data->rev) {
ret = sbrmi_get_rev(data);
if (ret < 0)
goto exit_unlock;
}
/* Extract thread from the input msg structure */
thread = msg->cpu_in_out >> CPUID_MCA_THRD_INDEX;
switch (data->rev) {
case 0x10:
/* CPUID protocol for REV 0x10 is not supported*/
ret = -EOPNOTSUPP;
goto exit_unlock;
case 0x20:
ret = rmi_cpuid_input(data, msg, thread);
if (ret)
goto exit_unlock;
break;
case 0x21:
case 0x31:
ret = rmi_cpuid_input_ext(data, msg, thread);
if (ret)
goto exit_unlock;
break;
default:
ret = -EOPNOTSUPP;
goto exit_unlock;
}
/*
* For RMI Rev 0x20, new h/w status bit is introduced. which is used
* by firmware to indicate completion of commands (0x71, 0x72, 0x73).
* wait for the status bit to be set by the hardware before
* reading the data out.
*/
ret = regmap_read_poll_timeout(data->regmap, SBRMI_STATUS, hw_status,
hw_status & HW_ALERT_MASK, 500, 2000000);
if (ret)
goto exit_unlock;
ret = regmap_bulk_read(data->regmap, CPUID_MCA_CMD,
&output, CPUID_RD_REG_LEN);
if (ret < 0)
goto exit_unlock;
ret = regmap_write(data->regmap, SBRMI_STATUS,
HW_ALERT_MASK);
if (ret < 0)
goto exit_unlock;
if (output.num_bytes != CPUID_RD_REG_LEN - 1) {
ret = -EMSGSIZE;
goto exit_unlock;
}
if (output.status) {
ret = -EPROTOTYPE;
msg->fw_ret_code = output.status;
goto exit_unlock;
}
msg->cpu_in_out = output.value;
exit_unlock:
if (ret < 0)
msg->cpu_in_out = 0;
mutex_unlock(&data->lock);
return ret;
}
static int rmi_mcamsr_input(struct sbrmi_data *data, struct apml_mcamsr_msg *msg,
u16 thread)
{
struct cpu_msr_indata input = {0};
int val = 0, ret;
/* Thread > 127, Thread128 CS register, 1'b1 needs to be set to 1 */
if (thread > 127) {
thread -= 128;
val = 1;
}
ret = regmap_write(data->regmap, SBRMI_THREAD128CS, val);
if (ret < 0)
return ret;
prepare_mca_msr_input_message(&input, thread,
msg->mcamsr_in_out & CPUID_MCA_FUNC_MASK);
return regmap_bulk_write(data->regmap, CPUID_MCA_CMD,
&input, MSR_WR_REG_LEN);
}
static int rmi_mcamsr_input_ext(struct sbrmi_data *data, struct apml_mcamsr_msg *msg,
u16 thread)
{
struct cpu_msr_indata_ext input = {0};
prepare_mca_msr_input_message_ext(&input, thread,
msg->mcamsr_in_out & CPUID_MCA_FUNC_MASK);
return regmap_bulk_write(data->regmap, CPUID_MCA_CMD,
&input, MSR_WR_REG_LEN_EXT);
}
/* MCA MSR protocol */
static int rmi_mca_msr_read(struct sbrmi_data *data,
struct apml_mcamsr_msg *msg)
{
struct cpu_msr_outdata output = {0};
int ret;
int hw_status;
u16 thread;
mutex_lock(&data->lock);
/* cache the rev value to identify if protocol is supported or not */
if (!data->rev) {
ret = sbrmi_get_rev(data);
if (ret < 0)
goto exit_unlock;
}
/* Extract thread from the input msg structure */
thread = msg->mcamsr_in_out >> CPUID_MCA_THRD_INDEX;
switch (data->rev) {
case 0x10:
/* MCAMSR protocol for REV 0x10 is not supported*/
ret = -EOPNOTSUPP;
goto exit_unlock;
case 0x20:
ret = rmi_mcamsr_input(data, msg, thread);
if (ret)
goto exit_unlock;
break;
case 0x21:
case 0x31:
ret = rmi_mcamsr_input_ext(data, msg, thread);
if (ret)
goto exit_unlock;
break;
default:
ret = -EOPNOTSUPP;
goto exit_unlock;
}
/*
* For RMI Rev 0x20, new h/w status bit is introduced. which is used
* by firmware to indicate completion of commands (0x71, 0x72, 0x73).
* wait for the status bit to be set by the hardware before
* reading the data out.
*/
ret = regmap_read_poll_timeout(data->regmap, SBRMI_STATUS, hw_status,
hw_status & HW_ALERT_MASK, 500, 2000000);
if (ret)
goto exit_unlock;
ret = regmap_bulk_read(data->regmap, CPUID_MCA_CMD,
&output, MSR_RD_REG_LEN);
if (ret < 0)
goto exit_unlock;
ret = regmap_write(data->regmap, SBRMI_STATUS,
HW_ALERT_MASK);
if (ret < 0)
goto exit_unlock;
if (output.num_bytes != MSR_RD_REG_LEN - 1) {
ret = -EMSGSIZE;
goto exit_unlock;
}
if (output.status) {
ret = -EPROTOTYPE;
msg->fw_ret_code = output.status;
goto exit_unlock;
}
msg->mcamsr_in_out = output.value;
exit_unlock:
mutex_unlock(&data->lock);
return ret;
}
int rmi_mailbox_xfer(struct sbrmi_data *data,
struct apml_mbox_msg *msg)
{
unsigned int bytes, ec;
int i, ret;
int sw_status;
u8 byte;
mutex_lock(&data->lock);
msg->fw_ret_code = 0;
/* Indicate firmware a command is to be serviced */
ret = regmap_write(data->regmap, SBRMI_INBNDMSG7, START_CMD);
if (ret < 0)
goto exit_unlock;
/* Write the command to SBRMI::InBndMsg_inst0 */
ret = regmap_write(data->regmap, SBRMI_INBNDMSG0, msg->cmd);
if (ret < 0)
goto exit_unlock;
/*
* For both read and write the initiator (BMC) writes
* Command Data In[31:0] to SBRMI::InBndMsg_inst[4:1]
* SBRMI_x3C(MSB):SBRMI_x39(LSB)
*/
for (i = 0; i < AMD_SBI_MB_DATA_SIZE; i++) {
byte = (msg->mb_in_out >> i * 8) & 0xff;
ret = regmap_write(data->regmap, SBRMI_INBNDMSG1 + i, byte);
if (ret < 0)
goto exit_unlock;
}
/*
* Write 0x01 to SBRMI::SoftwareInterrupt to notify firmware to
* perform the requested read or write command
*/
ret = regmap_write(data->regmap, SBRMI_SW_INTERRUPT, TRIGGER_MAILBOX);
if (ret < 0)
goto exit_unlock;
/*
* Firmware will write SBRMI::Status[SwAlertSts]=1 to generate
* an ALERT (if enabled) to initiator (BMC) to indicate completion
* of the requested command
*/
ret = regmap_read_poll_timeout(data->regmap, SBRMI_STATUS, sw_status,
sw_status & SW_ALERT_MASK, 500, 2000000);
if (ret)
goto exit_unlock;
ret = regmap_read(data->regmap, SBRMI_OUTBNDMSG7, &ec);
if (ret || ec)
goto exit_clear_alert;
/* Clear the input value before updating the output data */
msg->mb_in_out = 0;
/*
* For a read operation, the initiator (BMC) reads the firmware
* response Command Data Out[31:0] from SBRMI::OutBndMsg_inst[4:1]
* {SBRMI_x34(MSB):SBRMI_x31(LSB)}.
*/
for (i = 0; i < AMD_SBI_MB_DATA_SIZE; i++) {
ret = regmap_read(data->regmap,
SBRMI_OUTBNDMSG1 + i, &bytes);
if (ret < 0)
break;
msg->mb_in_out |= bytes << i * 8;
}
exit_clear_alert:
/*
* BMC must write 1'b1 to SBRMI::Status[SwAlertSts] to clear the
* ALERT to initiator
*/
ret = regmap_write(data->regmap, SBRMI_STATUS,
sw_status | SW_ALERT_MASK);
if (ec) {
ret = -EPROTOTYPE;
msg->fw_ret_code = ec;
}
exit_unlock:
mutex_unlock(&data->lock);
return ret;
}
static int apml_rmi_reg_xfer(struct sbrmi_data *data,
struct apml_reg_xfer_msg __user *arg)
{
struct apml_reg_xfer_msg msg = { 0 };
unsigned int data_read;
int ret;
/* Copy the structure from user */
if (copy_from_user(&msg, arg, sizeof(struct apml_reg_xfer_msg)))
return -EFAULT;
mutex_lock(&data->lock);
if (msg.rflag) {
ret = regmap_read(data->regmap, msg.reg_addr, &data_read);
if (!ret)
msg.data_in_out = data_read;
} else {
ret = regmap_write(data->regmap, msg.reg_addr, msg.data_in_out);
}
mutex_unlock(&data->lock);
if (msg.rflag && !ret)
if (copy_to_user(arg, &msg, sizeof(struct apml_reg_xfer_msg)))
return -EFAULT;
return ret;
}
static int apml_mailbox_xfer(struct sbrmi_data *data, struct apml_mbox_msg __user *arg)
{
struct apml_mbox_msg msg = { 0 };
int ret;
/* Copy the structure from user */
if (copy_from_user(&msg, arg, sizeof(struct apml_mbox_msg)))
return -EFAULT;
/* Mailbox protocol */
ret = rmi_mailbox_xfer(data, &msg);
if (ret && ret != -EPROTOTYPE)
return ret;
if (copy_to_user(arg, &msg, sizeof(struct apml_mbox_msg)))
return -EFAULT;
return ret;
}
static int apml_cpuid_xfer(struct sbrmi_data *data, struct apml_cpuid_msg __user *arg)
{
struct apml_cpuid_msg msg = { 0 };
int ret;
/* Copy the structure from user */
if (copy_from_user(&msg, arg, sizeof(struct apml_cpuid_msg)))
return -EFAULT;
/* CPUID Protocol */
ret = rmi_cpuid_read(data, &msg);
if (ret && ret != -EPROTOTYPE)
return ret;
if (copy_to_user(arg, &msg, sizeof(struct apml_cpuid_msg)))
return -EFAULT;
return ret;
}
static int apml_mcamsr_xfer(struct sbrmi_data *data, struct apml_mcamsr_msg __user *arg)
{
struct apml_mcamsr_msg msg = { 0 };
int ret;
/* Copy the structure from user */
if (copy_from_user(&msg, arg, sizeof(struct apml_mcamsr_msg)))
return -EFAULT;
/* MCAMSR Protocol */
ret = rmi_mca_msr_read(data, &msg);
if (ret && ret != -EPROTOTYPE)
return ret;
if (copy_to_user(arg, &msg, sizeof(struct apml_mcamsr_msg)))
return -EFAULT;
return ret;
}
static long sbrmi_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
struct sbrmi_data *data;
data = container_of(fp->private_data, struct sbrmi_data, sbrmi_misc_dev);
switch (cmd) {
case SBRMI_IOCTL_MBOX_CMD:
return apml_mailbox_xfer(data, argp);
case SBRMI_IOCTL_CPUID_CMD:
return apml_cpuid_xfer(data, argp);
case SBRMI_IOCTL_MCAMSR_CMD:
return apml_mcamsr_xfer(data, argp);
case SBRMI_IOCTL_REG_XFER_CMD:
return apml_rmi_reg_xfer(data, argp);
default:
return -ENOTTY;
}
}
static const struct file_operations sbrmi_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = sbrmi_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};
int create_misc_rmi_device(struct sbrmi_data *data,
struct device *dev)
{
data->sbrmi_misc_dev.name = devm_kasprintf(dev,
GFP_KERNEL,
"sbrmi-%x",
data->dev_static_addr);
data->sbrmi_misc_dev.minor = MISC_DYNAMIC_MINOR;
data->sbrmi_misc_dev.fops = &sbrmi_fops;
data->sbrmi_misc_dev.parent = dev;
data->sbrmi_misc_dev.nodename = devm_kasprintf(dev,
GFP_KERNEL,
"sbrmi-%x",
data->dev_static_addr);
data->sbrmi_misc_dev.mode = 0600;
return misc_register(&data->sbrmi_misc_dev);
}
@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#ifndef _SBRMI_CORE_H_
#define _SBRMI_CORE_H_
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <uapi/misc/amd-apml.h>
/* SB-RMI registers */
enum sbrmi_reg {
SBRMI_REV,
SBRMI_CTRL,
SBRMI_STATUS,
SBRMI_OUTBNDMSG0 = 0x30,
SBRMI_OUTBNDMSG1,
SBRMI_OUTBNDMSG2,
SBRMI_OUTBNDMSG3,
SBRMI_OUTBNDMSG4,
SBRMI_OUTBNDMSG5,
SBRMI_OUTBNDMSG6,
SBRMI_OUTBNDMSG7,
SBRMI_INBNDMSG0,
SBRMI_INBNDMSG1,
SBRMI_INBNDMSG2,
SBRMI_INBNDMSG3,
SBRMI_INBNDMSG4,
SBRMI_INBNDMSG5,
SBRMI_INBNDMSG6,
SBRMI_INBNDMSG7,
SBRMI_SW_INTERRUPT,
SBRMI_THREAD128CS = 0x4b,
};
/*
* SB-RMI supports soft mailbox service request to MP1 (power management
* firmware) through SBRMI inbound/outbound message registers.
* SB-RMI message IDs
*/
enum sbrmi_msg_id {
SBRMI_READ_PKG_PWR_CONSUMPTION = 0x1,
SBRMI_WRITE_PKG_PWR_LIMIT,
SBRMI_READ_PKG_PWR_LIMIT,
SBRMI_READ_PKG_MAX_PWR_LIMIT,
};
/* Each client has this additional data */
struct sbrmi_data {
struct miscdevice sbrmi_misc_dev;
struct regmap *regmap;
/* Mutex locking */
struct mutex lock;
u32 pwr_limit_max;
u8 dev_static_addr;
u8 rev;
};
int rmi_mailbox_xfer(struct sbrmi_data *data, struct apml_mbox_msg *msg);
#ifdef CONFIG_AMD_SBRMI_HWMON
int create_hwmon_sensor_device(struct device *dev, struct sbrmi_data *data);
#else
static inline int create_hwmon_sensor_device(struct device *dev, struct sbrmi_data *data)
{
return 0;
}
#endif
int create_misc_rmi_device(struct sbrmi_data *data, struct device *dev);
#endif /*_SBRMI_CORE_H_*/
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* rmi-hwmon.c - hwmon sensor support for side band RMI
*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#include <linux/err.h>
#include <linux/hwmon.h>
#include <uapi/misc/amd-apml.h>
#include "rmi-core.h"
/* Do not allow setting negative power limit */
#define SBRMI_PWR_MIN 0
static int sbrmi_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct sbrmi_data *data = dev_get_drvdata(dev);
struct apml_mbox_msg msg = { 0 };
int ret;
if (!data)
return -ENODEV;
if (type != hwmon_power)
return -EINVAL;
switch (attr) {
case hwmon_power_input:
msg.cmd = SBRMI_READ_PKG_PWR_CONSUMPTION;
ret = rmi_mailbox_xfer(data, &msg);
break;
case hwmon_power_cap:
msg.cmd = SBRMI_READ_PKG_PWR_LIMIT;
ret = rmi_mailbox_xfer(data, &msg);
break;
case hwmon_power_cap_max:
msg.mb_in_out = data->pwr_limit_max;
ret = 0;
break;
default:
return -EINVAL;
}
if (ret < 0)
return ret;
/* hwmon power attributes are in microWatt */
*val = (long)msg.mb_in_out * 1000;
return ret;
}
static int sbrmi_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct sbrmi_data *data = dev_get_drvdata(dev);
struct apml_mbox_msg msg = { 0 };
if (!data)
return -ENODEV;
if (type != hwmon_power && attr != hwmon_power_cap)
return -EINVAL;
/*
* hwmon power attributes are in microWatt
* mailbox read/write is in mWatt
*/
val /= 1000;
val = clamp_val(val, SBRMI_PWR_MIN, data->pwr_limit_max);
msg.cmd = SBRMI_WRITE_PKG_PWR_LIMIT;
msg.mb_in_out = val;
return rmi_mailbox_xfer(data, &msg);
}
static umode_t sbrmi_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_power:
switch (attr) {
case hwmon_power_input:
case hwmon_power_cap_max:
return 0444;
case hwmon_power_cap:
return 0644;
}
break;
default:
break;
}
return 0;
}
static const struct hwmon_channel_info * const sbrmi_info[] = {
HWMON_CHANNEL_INFO(power,
HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CAP_MAX),
NULL
};
static const struct hwmon_ops sbrmi_hwmon_ops = {
.is_visible = sbrmi_is_visible,
.read = sbrmi_read,
.write = sbrmi_write,
};
static const struct hwmon_chip_info sbrmi_chip_info = {
.ops = &sbrmi_hwmon_ops,
.info = sbrmi_info,
};
int create_hwmon_sensor_device(struct device *dev, struct sbrmi_data *data)
{
struct device *hwmon_dev;
hwmon_dev = devm_hwmon_device_register_with_info(dev, "sbrmi", data,
&sbrmi_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
+247
View File
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* rmi-i2c.c - Side band RMI over I2C support for AMD out
* of band management
*
* Copyright (C) 2024 Advanced Micro Devices, Inc.
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/i3c/device.h>
#include <linux/i3c/master.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include "rmi-core.h"
#define REV_TWO_BYTE_ADDR 0x21
static int sbrmi_enable_alert(struct sbrmi_data *data)
{
int ctrl, ret;
/*
* Enable the SB-RMI Software alert status
* by writing 0 to bit 4 of Control register(0x1)
*/
ret = regmap_read(data->regmap, SBRMI_CTRL, &ctrl);
if (ret < 0)
return ret;
if (ctrl & 0x10) {
ctrl &= ~0x10;
return regmap_write(data->regmap, SBRMI_CTRL, ctrl);
}
return 0;
}
static int sbrmi_get_max_pwr_limit(struct sbrmi_data *data)
{
struct apml_mbox_msg msg = { 0 };
int ret;
msg.cmd = SBRMI_READ_PKG_MAX_PWR_LIMIT;
ret = rmi_mailbox_xfer(data, &msg);
if (ret < 0)
return ret;
data->pwr_limit_max = msg.mb_in_out;
return ret;
}
static int sbrmi_common_probe(struct device *dev, struct regmap *regmap, uint8_t address)
{
struct sbrmi_data *data;
int ret;
data = devm_kzalloc(dev, sizeof(struct sbrmi_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->regmap = regmap;
mutex_init(&data->lock);
/* Enable alert for SB-RMI sequence */
ret = sbrmi_enable_alert(data);
if (ret < 0)
return ret;
/* Cache maximum power limit */
ret = sbrmi_get_max_pwr_limit(data);
if (ret < 0)
return ret;
data->dev_static_addr = address;
dev_set_drvdata(dev, data);
ret = create_hwmon_sensor_device(dev, data);
if (ret < 0)
return ret;
return create_misc_rmi_device(data, dev);
}
static struct regmap_config sbrmi_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
};
static struct regmap_config sbrmi_regmap_config_ext = {
.reg_bits = 16,
.val_bits = 8,
.reg_format_endian = REGMAP_ENDIAN_LITTLE,
};
static int sbrmi_i2c_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct regmap *regmap;
int rev, ret;
regmap = devm_regmap_init_i2c(client, &sbrmi_regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
ret = regmap_read(regmap, SBRMI_REV, &rev);
if (ret)
return ret;
/*
* For Turin and newer platforms, revision is 0x21 or later. This is
* to identify the two byte register address size. However, one
* byte transaction can be successful.
* Verify if revision is 0x21 or later, if yes, switch to 2 byte
* address size.
* Continuously using 1 byte address for revision 0x21 or later can lead
* to bus corruption.
*/
if (rev >= REV_TWO_BYTE_ADDR) {
regmap = devm_regmap_init_i2c(client, &sbrmi_regmap_config_ext);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
}
return sbrmi_common_probe(dev, regmap, client->addr);
}
static void sbrmi_i2c_remove(struct i2c_client *client)
{
struct sbrmi_data *data = dev_get_drvdata(&client->dev);
misc_deregister(&data->sbrmi_misc_dev);
/* Assign fops and parent of misc dev to NULL */
data->sbrmi_misc_dev.fops = NULL;
data->sbrmi_misc_dev.parent = NULL;
dev_info(&client->dev, "Removed sbrmi-i2c driver\n");
return;
}
static const struct i2c_device_id sbrmi_id[] = {
{"sbrmi-i2c"},
{}
};
MODULE_DEVICE_TABLE(i2c, sbrmi_id);
static const struct of_device_id __maybe_unused sbrmi_of_match[] = {
{
.compatible = "amd,sbrmi",
},
{ },
};
MODULE_DEVICE_TABLE(of, sbrmi_of_match);
static struct i2c_driver sbrmi_driver = {
.driver = {
.name = "sbrmi-i2c",
.of_match_table = of_match_ptr(sbrmi_of_match),
},
.probe = sbrmi_i2c_probe,
.remove = sbrmi_i2c_remove,
.id_table = sbrmi_id,
};
static int sbrmi_i3c_probe(struct i3c_device *i3cdev)
{
struct device *dev = i3cdev_to_dev(i3cdev);
struct regmap *regmap;
int rev, ret;
/*
* AMD OOB devices are distinguished by their Instance ID.
* For SBRMI, the Instance ID is 1. Since the device ID match
* does not account for the Instance ID, the following check
* ensures that only the SBRMI device is probed, excluding
* other OOB devices.
*/
if (I3C_PID_INSTANCE_ID(i3cdev->desc->info.pid) != 1)
return -ENXIO;
regmap = devm_regmap_init_i3c(i3cdev, &sbrmi_regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
ret = regmap_read(regmap, SBRMI_REV, &rev);
if (ret)
return ret;
/*
* For Turin and newer platforms, revision is 0x21 or later. This is
* to identify the two byte register address size. However, one
* byte transaction can be successful.
* Verify if revision is 0x21 or later, if yes, switch to 2 byte
* address size.
* Continuously using 1 byte address for revision 0x21 or later can lead
* to bus corruption.
*/
if (rev >= REV_TWO_BYTE_ADDR) {
regmap = devm_regmap_init_i3c(i3cdev, &sbrmi_regmap_config_ext);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
}
/*
* AMD APML I3C devices support static address.
* If static address is defined, dynamic address is same as static address.
* In case static address is not defined, I3C master controller defined
* dynamic address is used.
*/
return sbrmi_common_probe(dev, regmap, i3cdev->desc->info.dyn_addr);
}
static void sbrmi_i3c_remove(struct i3c_device *i3cdev)
{
struct sbrmi_data *data = dev_get_drvdata(&i3cdev->dev);
misc_deregister(&data->sbrmi_misc_dev);
}
static const struct i3c_device_id sbrmi_i3c_id[] = {
/* PID for AMD SBRMI device */
I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x2, NULL),
I3C_DEVICE_EXTRA_INFO(0x0, 0x0, 0x118, NULL), /* Socket:0, Venice */
I3C_DEVICE_EXTRA_INFO(0x0, 0x100, 0x118, NULL), /* Socket:1, Venice */
I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x119, NULL), /* Socket:0, Venice */
I3C_DEVICE_EXTRA_INFO(0x112, 0x100, 0x119, NULL), /* Socket:1, Venice */
{}
};
MODULE_DEVICE_TABLE(i3c, sbrmi_i3c_id);
static struct i3c_driver sbrmi_i3c_driver = {
.driver = {
.name = "sbrmi-i3c",
},
.probe = sbrmi_i3c_probe,
.remove = sbrmi_i3c_remove,
.id_table = sbrmi_i3c_id,
};
module_i3c_i2c_driver(sbrmi_i3c_driver, &sbrmi_driver);
MODULE_AUTHOR("Akshay Gupta <akshay.gupta@amd.com>");
MODULE_AUTHOR("Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>");
MODULE_DESCRIPTION("Hwmon driver for AMD SB-RMI emulated sensor");
MODULE_LICENSE("GPL");