platform/x86: ISST: Parse SST MMIO and update instance
authorSrinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Wed, 8 Mar 2023 07:06:37 +0000 (23:06 -0800)
committerHans de Goede <hdegoede@redhat.com>
Thu, 16 Mar 2023 14:18:02 +0000 (15:18 +0100)
SST registers are presented to OS in multi-layer structures starting
with a SST header showing version information freezing current
definition.

For details on SST terminology refer to
Documentation/admin-guide/pm/intel-speed-select.rst
under the kernel documentation

SST TPMI details are published in the following document:
https://github.com/intel/tpmi_power_management/blob/main/SST_TPMI_public_disclosure_FINAL.docx

SST MMIO structure layout follows:
SST-HEADER
SST-CP Header
SST-CP CONTROL
SST-CP STATUS
SST-CP CONFIG0
SST-CP CONFIG1
...
...
SST-PP Header
SST-PP OFFSET_0
SST-PP OFFSET_1
SST_PP_0_INFO
SST_PP_1_INFO
SST_PP_2_INFO
SST_PP_3_INFO
SST-PP CONTROL
SST-PP STATUS

Each register bank contains information to get to next lower level
information. This information is parsed and stored in the struct
tpmi_per_power_domain_info for each domain. This information is
used to process each SST requests.

Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Tested-by: Pragya Tanwar <pragya.tanwar@intel.com>
Link: https://lore.kernel.org/r/20230308070642.1727167-4-srinivas.pandruvada@linux.intel.com
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c

index 6b37016..3453708 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/auxiliary_bus.h>
 #include <linux/intel_tpmi.h>
 #include <linux/fs.h>
+#include <linux/io.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <uapi/linux/isst_if.h>
 #include "isst_tpmi_core.h"
 #include "isst_if_common.h"
 
+/* Supported SST hardware version by this driver */
+#define ISST_HEADER_VERSION            1
+
+/**
+ * struct sst_header - SST main header
+ * @interface_version: Version number for this interface
+ * @cap_mask:          Bitmask of the supported sub features. 1=the sub feature is enabled.
+ *                     0=disabled.
+ *                     Bit[8]= SST_CP enable (1), disable (0)
+ *                     bit[9]= SST_PP enable (1), disable (0)
+ *                     other bits are reserved for future use
+ * @cp_offset:         Qword (8 bytes) offset to the SST_CP register bank
+ * @pp_offset:         Qword (8 bytes) offset to the SST_PP register bank
+ * @reserved:          Reserved for future use
+ *
+ * This register allows SW to discover SST capability and the offsets to SST-CP
+ * and SST-PP register banks.
+ */
+struct sst_header {
+       u8 interface_version;
+       u8 cap_mask;
+       u8 cp_offset;
+       u8 pp_offset;
+       u32 reserved;
+} __packed;
+
+/**
+ * struct cp_header -  SST-CP (core-power) header
+ * @feature_id:                0=SST-CP, 1=SST-PP, 2=SST-BF, 3=SST-TF
+ * @feature_rev:       Interface Version number for this SST feature
+ * @ratio_unit:                Frequency ratio unit. 00: 100MHz. All others are reserved
+ * @reserved:          Reserved for future use
+ *
+ * This structure is used store SST-CP header. This is packed to the same
+ * format as defined in the specifications.
+ */
+struct cp_header {
+       u64 feature_id :4;
+       u64 feature_rev :8;
+       u64 ratio_unit :2;
+       u64 reserved :50;
+} __packed;
+
+/**
+ * struct pp_header -  SST-PP (Perf profile) header
+ * @feature_id:                0=SST-CP, 1=SST-PP, 2=SST-BF, 3=SST-TF
+ * @feature_rev:       Interface Version number for this SST feature
+ * @level_en_mask:     SST-PP level enable/disable fuse mask
+ * @allowed_level_mask:        Allowed level mask used for dynamic config level switching
+ * @reserved0:         Reserved for future use
+ * @ratio_unit:                Frequency ratio unit. 00: 100MHz. All others are reserved
+ * @block_size:                Size of PP block in Qword unit (8 bytes)
+ * @dynamic_switch:    If set (1), dynamic switching of SST PP is supported
+ * @memory_ratio_unit: Memory Controller frequency ratio unit. 00: 100MHz, others reserved
+ * @reserved1:         Reserved for future use
+ *
+ * This structure is used store SST-PP header. This is packed to the same
+ * format as defined in the specifications.
+ */
+struct pp_header {
+       u64 feature_id :4;
+       u64 feature_rev :8;
+       u64 level_en_mask :8;
+       u64 allowed_level_mask :8;
+       u64 reserved0 :4;
+       u64 ratio_unit :2;
+       u64 block_size :8;
+       u64 dynamic_switch :1;
+       u64 memory_ratio_unit :2;
+       u64 reserved1 :19;
+} __packed;
+
+/**
+ * struct feature_offset -     Offsets to SST-PP features
+ * @pp_offset:         Qword offset within PP level for the SST_PP register bank
+ * @bf_offset:         Qword offset within PP level for the SST_BF register bank
+ * @tf_offset:         Qword offset within PP level for the SST_TF register bank
+ * @reserved:          Reserved for future use
+ *
+ * This structure is used store offsets for SST features in the register bank.
+ * This is packed to the same format as defined in the specifications.
+ */
+struct feature_offset {
+       u64 pp_offset :8;
+       u64 bf_offset :8;
+       u64 tf_offset :8;
+       u64 reserved :40;
+} __packed;
+
+/**
+ * struct levels_offset -      Offsets to each SST PP level
+ * @sst_pp_level0_offset:      Qword offset to the register block of PP level 0
+ * @sst_pp_level1_offset:      Qword offset to the register block of PP level 1
+ * @sst_pp_level2_offset:      Qword offset to the register block of PP level 2
+ * @sst_pp_level3_offset:      Qword offset to the register block of PP level 3
+ * @sst_pp_level4_offset:      Qword offset to the register block of PP level 4
+ * @reserved:                  Reserved for future use
+ *
+ * This structure is used store offsets of SST PP levels in the register bank.
+ * This is packed to the same format as defined in the specifications.
+ */
+struct levels_offset {
+       u64 sst_pp_level0_offset :8;
+       u64 sst_pp_level1_offset :8;
+       u64 sst_pp_level2_offset :8;
+       u64 sst_pp_level3_offset :8;
+       u64 sst_pp_level4_offset :8;
+       u64 reserved :24;
+} __packed;
+
+/**
+ * struct pp_control_offset -  Offsets for SST PP controls
+ * @perf_level:                A SST-PP level that SW intends to switch to
+ * @perf_level_lock:   SST-PP level select lock. 0 - unlocked. 1 - locked till next reset
+ * @resvd0:            Reserved for future use
+ * @current_state:     Bit mask to control the enable(1)/disable(0) state of each feature
+ *                     of the current PP level, bit 0 = BF, bit 1 = TF, bit 2-7 = reserved
+ * @reserved:          Reserved for future use
+ *
+ * This structure is used store offsets of SST PP controls in the register bank.
+ * This is packed to the same format as defined in the specifications.
+ */
+struct pp_control_offset {
+       u64 perf_level :3;
+       u64 perf_level_lock :1;
+       u64 resvd0 :4;
+       u64 current_state :8;
+       u64 reserved :48;
+} __packed;
+
+/**
+ * struct pp_status_offset -   Offsets for SST PP status fields
+ * @sst_pp_level:      Returns the current SST-PP level
+ * @sst_pp_lock:       Returns the lock bit setting of perf_level_lock in pp_control_offset
+ * @error_type:                Returns last error of SST-PP level change request. 0: no error,
+ *                     1: level change not allowed, others: reserved
+ * @feature_state:     Bit mask to indicate the enable(1)/disable(0) state of each feature of the
+ *                     current PP level. bit 0 = BF, bit 1 = TF, bit 2-7 reserved
+ * @reserved0:         Reserved for future use
+ * @feature_error_type: Returns last error of the specific feature. Three error_type bits per
+ *                     feature. i.e. ERROR_TYPE[2:0] for BF, ERROR_TYPE[5:3] for TF, etc.
+ *                     0x0: no error, 0x1: The specific feature is not supported by the hardware.
+ *                     0x2-0x6: Reserved. 0x7: feature state change is not allowed.
+ * @reserved1:         Reserved for future use
+ *
+ * This structure is used store offsets of SST PP status in the register bank.
+ * This is packed to the same format as defined in the specifications.
+ */
+struct pp_status_offset {
+       u64 sst_pp_level :3;
+       u64 sst_pp_lock :1;
+       u64 error_type :4;
+       u64 feature_state :8;
+       u64 reserved0 :16;
+       u64 feature_error_type : 24;
+       u64 reserved1 :8;
+} __packed;
+
+/**
+ * struct perf_level - Used to store perf level and mmio offset
+ * @mmio_offset:       mmio offset for a perf level
+ * @level:             perf level for this offset
+ *
+ * This structure is used store final mmio offset of each perf level from the
+ * SST base mmio offset.
+ */
+struct perf_level {
+       int mmio_offset;
+       int level;
+};
+
 /**
  * struct tpmi_per_power_domain_info - Store per power_domain SST info
  * @package_id:                Package id for this power_domain
  * @power_domain_id:   Power domain id, Each entry from the SST-TPMI instance is a power_domain.
+ * @max_level:         Max possible PP level possible for this power_domain
+ * @ratio_unit:                Ratio unit for converting to MHz
+ * @avx_levels:                Number of AVX levels
+ * @pp_block_size:     Block size from PP header
+ * @sst_header:                Store SST header for this power_domain
+ * @cp_header:         Store SST-CP header for this power_domain
+ * @pp_header:         Store SST-PP header for this power_domain
+ * @perf_levels:       Pointer to each perf level to map level to mmio offset
+ * @feature_offsets:   Store feature offsets for each PP-level
+ * @control_offset:    Store the control offset for each PP-level
+ * @status_offset:     Store the status offset for each PP-level
  * @sst_base:          Mapped SST base IO memory
  * @auxdev:            Auxiliary device instance enumerated this instance
  *
 struct tpmi_per_power_domain_info {
        int package_id;
        int power_domain_id;
+       int max_level;
+       int ratio_unit;
+       int avx_levels;
+       int pp_block_size;
+       struct sst_header sst_header;
+       struct cp_header cp_header;
+       struct pp_header pp_header;
+       struct perf_level *perf_levels;
+       struct feature_offset feature_offsets;
+       struct pp_control_offset control_offset;
+       struct pp_status_offset status_offset;
        void __iomem *sst_base;
        struct auxiliary_device *auxdev;
 };
@@ -85,6 +279,86 @@ static int isst_core_usage_count;
 /* Stores complete SST information for every package and power_domain */
 static struct tpmi_sst_common_struct isst_common;
 
+#define SST_MAX_AVX_LEVELS     3
+
+#define SST_PP_OFFSET_0                8
+#define SST_PP_OFFSET_1                16
+#define SST_PP_OFFSET_SIZE     8
+
+static int sst_add_perf_profiles(struct auxiliary_device *auxdev,
+                                struct tpmi_per_power_domain_info *pd_info,
+                                int levels)
+{
+       u64 perf_level_offsets;
+       int i;
+
+       pd_info->perf_levels = devm_kcalloc(&auxdev->dev, levels,
+                                           sizeof(struct perf_level),
+                                           GFP_KERNEL);
+       if (!pd_info->perf_levels)
+               return 0;
+
+       pd_info->ratio_unit = pd_info->pp_header.ratio_unit;
+       pd_info->avx_levels = SST_MAX_AVX_LEVELS;
+       pd_info->pp_block_size = pd_info->pp_header.block_size;
+
+       /* Read PP Offset 0: Get feature offset with PP level */
+       *((u64 *)&pd_info->feature_offsets) = readq(pd_info->sst_base +
+                                                   pd_info->sst_header.pp_offset +
+                                                   SST_PP_OFFSET_0);
+
+       perf_level_offsets = readq(pd_info->sst_base + pd_info->sst_header.pp_offset +
+                                  SST_PP_OFFSET_1);
+
+       for (i = 0; i < levels; ++i) {
+               u64 offset;
+
+               offset = perf_level_offsets & (0xff << (i * SST_PP_OFFSET_SIZE));
+               offset >>= (i * 8);
+               offset &= 0xff;
+               offset *= 8; /* Convert to byte from QWORD offset */
+               pd_info->perf_levels[i].mmio_offset = pd_info->sst_header.pp_offset + offset;
+       }
+
+       return 0;
+}
+
+static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domain_info *pd_info)
+{
+       int i, mask, levels;
+
+       *((u64 *)&pd_info->sst_header) = readq(pd_info->sst_base);
+       pd_info->sst_header.cp_offset *= 8;
+       pd_info->sst_header.pp_offset *= 8;
+
+       if (pd_info->sst_header.interface_version != ISST_HEADER_VERSION) {
+               dev_err(&auxdev->dev, "SST: Unsupported version:%x\n",
+                       pd_info->sst_header.interface_version);
+               return -ENODEV;
+       }
+
+       /* Read SST CP Header */
+       *((u64 *)&pd_info->cp_header) = readq(pd_info->sst_base + pd_info->sst_header.cp_offset);
+
+       /* Read PP header */
+       *((u64 *)&pd_info->pp_header) = readq(pd_info->sst_base + pd_info->sst_header.pp_offset);
+
+       /* Force level_en_mask level 0 */
+       pd_info->pp_header.level_en_mask |= 0x01;
+
+       mask = 0x01;
+       levels = 0;
+       for (i = 0; i < 8; ++i) {
+               if (pd_info->pp_header.level_en_mask & mask)
+                       levels = i;
+               mask <<= 1;
+       }
+       pd_info->max_level = levels;
+       sst_add_perf_profiles(auxdev, pd_info, levels + 1);
+
+       return 0;
+}
+
 static int isst_if_get_tpmi_instance_count(void __user *argp)
 {
        struct isst_tpmi_instance_count tpmi_inst;
@@ -102,10 +376,10 @@ static int isst_if_get_tpmi_instance_count(void __user *argp)
        sst_inst = isst_common.sst_inst[tpmi_inst.socket_id];
        tpmi_inst.valid_mask = 0;
        for (i = 0; i < sst_inst->number_of_power_domains; ++i) {
-               struct tpmi_per_power_domain_info *power_domain_info;
+               struct tpmi_per_power_domain_info *pd_info;
 
-               power_domain_info = &sst_inst->power_domain_info[i];
-               if (power_domain_info->sst_base)
+               pd_info = &sst_inst->power_domain_info[i];
+               if (pd_info->sst_base)
                        tpmi_inst.valid_mask |= BIT(i);
        }
 
@@ -134,11 +408,13 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
        return ret;
 }
 
+#define TPMI_SST_AUTO_SUSPEND_DELAY_MS 2000
+
 int tpmi_sst_dev_add(struct auxiliary_device *auxdev)
 {
        struct intel_tpmi_plat_info *plat_info;
        struct tpmi_sst_struct *tpmi_sst;
-       int i, pkg = 0, inst = 0;
+       int i, ret, pkg = 0, inst = 0;
        int num_resources;
 
        plat_info = tpmi_get_platform_data(auxdev);
@@ -189,6 +465,13 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev)
                if (IS_ERR(tpmi_sst->power_domain_info[i].sst_base))
                        return PTR_ERR(tpmi_sst->power_domain_info[i].sst_base);
 
+               ret = sst_main(auxdev, &tpmi_sst->power_domain_info[i]);
+               if (ret) {
+                       devm_iounmap(&auxdev->dev, tpmi_sst->power_domain_info[i].sst_base);
+                       tpmi_sst->power_domain_info[i].sst_base =  NULL;
+                       continue;
+               }
+
                ++inst;
        }