root/drivers/misc/amd-sbi/rmi-core.c
// 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);
}