diff --git a/patch/0028-thermal-Fix-deadlock-in-thermal-thermal_zone_device_.patch b/patch/0028-thermal-Fix-deadlock-in-thermal-thermal_zone_device_.patch new file mode 100644 index 000000000000..55b93426b3f4 --- /dev/null +++ b/patch/0028-thermal-Fix-deadlock-in-thermal-thermal_zone_device_.patch @@ -0,0 +1,83 @@ +From 163b00cde7cf2206e248789d2780121ad5e6a70b Mon Sep 17 00:00:00 2001 +From: Wei Wang +Date: Tue, 12 Nov 2019 12:42:23 -0800 +Subject: [PATCH] thermal: Fix deadlock in thermal thermal_zone_device_check + +1851799e1d29 ("thermal: Fix use-after-free when unregistering thermal zone +device") changed cancel_delayed_work to cancel_delayed_work_sync to avoid +a use-after-free issue. However, cancel_delayed_work_sync could be called +insides the WQ causing deadlock. + +[54109.642398] c0 1162 kworker/u17:1 D 0 11030 2 0x00000000 +[54109.642437] c0 1162 Workqueue: thermal_passive_wq thermal_zone_device_check +[54109.642447] c0 1162 Call trace: +[54109.642456] c0 1162 __switch_to+0x138/0x158 +[54109.642467] c0 1162 __schedule+0xba4/0x1434 +[54109.642480] c0 1162 schedule_timeout+0xa0/0xb28 +[54109.642492] c0 1162 wait_for_common+0x138/0x2e8 +[54109.642511] c0 1162 flush_work+0x348/0x40c +[54109.642522] c0 1162 __cancel_work_timer+0x180/0x218 +[54109.642544] c0 1162 handle_thermal_trip+0x2c4/0x5a4 +[54109.642553] c0 1162 thermal_zone_device_update+0x1b4/0x25c +[54109.642563] c0 1162 thermal_zone_device_check+0x18/0x24 +[54109.642574] c0 1162 process_one_work+0x3cc/0x69c +[54109.642583] c0 1162 worker_thread+0x49c/0x7c0 +[54109.642593] c0 1162 kthread+0x17c/0x1b0 +[54109.642602] c0 1162 ret_from_fork+0x10/0x18 +[54109.643051] c0 1162 kworker/u17:2 D 0 16245 2 0x00000000 +[54109.643067] c0 1162 Workqueue: thermal_passive_wq thermal_zone_device_check +[54109.643077] c0 1162 Call trace: +[54109.643085] c0 1162 __switch_to+0x138/0x158 +[54109.643095] c0 1162 __schedule+0xba4/0x1434 +[54109.643104] c0 1162 schedule_timeout+0xa0/0xb28 +[54109.643114] c0 1162 wait_for_common+0x138/0x2e8 +[54109.643122] c0 1162 flush_work+0x348/0x40c +[54109.643131] c0 1162 __cancel_work_timer+0x180/0x218 +[54109.643141] c0 1162 handle_thermal_trip+0x2c4/0x5a4 +[54109.643150] c0 1162 thermal_zone_device_update+0x1b4/0x25c +[54109.643159] c0 1162 thermal_zone_device_check+0x18/0x24 +[54109.643167] c0 1162 process_one_work+0x3cc/0x69c +[54109.643177] c0 1162 worker_thread+0x49c/0x7c0 +[54109.643186] c0 1162 kthread+0x17c/0x1b0 +[54109.643195] c0 1162 ret_from_fork+0x10/0x18 +[54109.644500] c0 1162 cat D 0 7766 1 0x00000001 +[54109.644515] c0 1162 Call trace: +[54109.644524] c0 1162 __switch_to+0x138/0x158 +[54109.644536] c0 1162 __schedule+0xba4/0x1434 +[54109.644546] c0 1162 schedule_preempt_disabled+0x80/0xb0 +[54109.644555] c0 1162 __mutex_lock+0x3a8/0x7f0 +[54109.644563] c0 1162 __mutex_lock_slowpath+0x14/0x20 +[54109.644575] c0 1162 thermal_zone_get_temp+0x84/0x360 +[54109.644586] c0 1162 temp_show+0x30/0x78 +[54109.644609] c0 1162 dev_attr_show+0x5c/0xf0 +[54109.644628] c0 1162 sysfs_kf_seq_show+0xcc/0x1a4 +[54109.644636] c0 1162 kernfs_seq_show+0x48/0x88 +[54109.644656] c0 1162 seq_read+0x1f4/0x73c +[54109.644664] c0 1162 kernfs_fop_read+0x84/0x318 +[54109.644683] c0 1162 __vfs_read+0x50/0x1bc +[54109.644692] c0 1162 vfs_read+0xa4/0x140 +[54109.644701] c0 1162 SyS_read+0xbc/0x144 +[54109.644708] c0 1162 el0_svc_naked+0x34/0x38 +[54109.845800] c0 1162 D 720.000s 1->7766->7766 cat [panic] + +Fixes: 1851799e1d29 ("thermal: Fix use-after-free when unregistering thermal zone device") +Cc: stable@vger.kernel.org +Signed-off-by: Wei Wang +Signed-off-by: Zhang Rui +--- + drivers/thermal/thermal_core.c | 2 ++-- + 1 file changed, 1 insertions(+), 1 deletions(-) + +diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c +index 7b0ffc1c0..f5a84db55 100644 +--- a/drivers/thermal/thermal_core.c ++++ b/drivers/thermal/thermal_core.c +@@ -296,7 +296,7 @@ static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, + mod_delayed_work(system_freezable_wq, &tz->poll_queue, + msecs_to_jiffies(delay)); + else +- cancel_delayed_work(&tz->poll_queue); ++ cancel_delayed_work_sync(&tz->poll_queue); + } + + static void monitor_thermal_zone(struct thermal_zone_device *tz) diff --git a/patch/0029-hwmon-pmbus-Add-support-for-MPS-Multi-phase-mp2975-c.patch b/patch/0029-hwmon-pmbus-Add-support-for-MPS-Multi-phase-mp2975-c.patch new file mode 100644 index 000000000000..2ee1b2e05c7d --- /dev/null +++ b/patch/0029-hwmon-pmbus-Add-support-for-MPS-Multi-phase-mp2975-c.patch @@ -0,0 +1,874 @@ +From 4218cfb886d2de82677a3fe1aeb44858c38f81bd Mon Sep 17 00:00:00 2001 +From: Oleksandr Shamray +Date: Thu, 12 Nov 2020 16:16:50 +0200 +Subject: [PATCH 1/4] hwmon: (pmbus) Add support for MPS Multi-phase mp2975 + controller + +Add support for mp295 device from Monolithic Power Systems, Inc. (MPS) +vendor. This is a dual-loop, digital, multi-phase controller. +This device: +- Supports two power rail. +- Provides 8 pulse-width modulations (PWMs), and can be configured up + to 8-phase operation for rail 1 and up to 4-phase operation for rail + 2. +- Supports two pages 0 and 1 for telemetry and also pages 2 and 3 for + configuration. +- Can configured VOUT readout in direct or VID format and allows + setting of different formats on rails 1 and 2. For VID the following + protocols are available: VR13 mode with 5-mV DAC; VR13 mode with + 10-mV DAC, IMVP9 mode with 5-mV DAC. + +Signed-off-by: Vadim Pasternak +--- + drivers/hwmon/pmbus/Kconfig | 10 + + drivers/hwmon/pmbus/Makefile | 1 + + drivers/hwmon/pmbus/mp2975.c | 804 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 815 insertions(+) + create mode 100644 drivers/hwmon/pmbus/mp2975.c + +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +index 14c0572..fbf326e 100644 +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -145,6 +145,16 @@ config SENSORS_MAX8688 + This driver can also be built as a module. If so, the module will + be called max8688. + ++config SENSORS_MP2975 ++ tristate "MPS MP2975" ++ default n ++ help ++ If you say yes here you get hardware monitoring support for MPS ++ MP2975 Dual Loop Digital Multi-Phase Controller. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mp2975. ++ + config SENSORS_TPS40422 + tristate "TI TPS40422" + default n +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +index edc7c315c..3f83d8582 100644 +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_MAX31785) += max31785.o + obj-$(CONFIG_SENSORS_MAX34440) += max34440.o + obj-$(CONFIG_SENSORS_DNI_DPS460) += dni_dps460.o + obj-$(CONFIG_SENSORS_MAX8688) += max8688.o ++obj-$(CONFIG_SENSORS_MP2975) += mp2975.o + obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o + obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o + obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o +diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c +new file mode 100644 +index 0000000..a0c2bcf +--- /dev/null ++++ b/drivers/hwmon/pmbus/mp2975.c +@@ -0,0 +1,804 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers ++ * ++ * Copyright (c) 2020 Nvidia Technologies. All rights reserved. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++/* Vendor specific registers. */ ++#define MP2975_MFR_APS_HYS_R2 0x0d ++#define MP2975_MFR_SLOPE_TRIM3 0x1d ++#define MP2975_MFR_VR_MULTI_CONFIG_R1 0x0d ++#define MP2975_MFR_VR_MULTI_CONFIG_R2 0x1d ++#define MP2975_MFR_APS_DECAY_ADV 0x56 ++#define MP2975_MFR_DC_LOOP_CTRL 0x59 ++#define MP2975_MFR_OCP_UCP_PHASE_SET 0x65 ++#define MP2975_MFR_VR_CONFIG1 0x68 ++#define MP2975_MFR_READ_CS1_2 0x82 ++#define MP2975_MFR_READ_CS3_4 0x83 ++#define MP2975_MFR_READ_CS5_6 0x84 ++#define MP2975_MFR_READ_CS7_8 0x85 ++#define MP2975_MFR_READ_CS9_10 0x86 ++#define MP2975_MFR_READ_CS11_12 0x87 ++#define MP2975_MFR_READ_IOUT_PK 0x90 ++#define MP2975_MFR_READ_POUT_PK 0x91 ++#define MP2975_MFR_READ_VREF_R1 0xa1 ++#define MP2975_MFR_READ_VREF_R2 0xa3 ++#define MP2975_MFR_OVP_TH_SET 0xe5 ++#define MP2975_MFR_UVP_SET 0xe6 ++ ++#define MP2975_VOUT_FORMAT BIT(15) ++#define MP2975_VID_STEP_SEL_R1 BIT(4) ++#define MP2975_IMVP9_EN_R1 BIT(13) ++#define MP2975_VID_STEP_SEL_R2 BIT(3) ++#define MP2975_IMVP9_EN_R2 BIT(12) ++#define MP2975_PRT_THRES_DIV_OV_EN BIT(14) ++#define MP2975_DRMOS_KCS GENMASK(13, 12) ++#define MP2975_PROT_DEV_OV_OFF 10 ++#define MP2975_PROT_DEV_OV_ON 5 ++#define MP2975_SENSE_AMPL BIT(11) ++#define MP2975_SENSE_AMPL_UNIT 1 ++#define MP2975_SENSE_AMPL_HALF 2 ++#define MP2975_VIN_UV_LIMIT_UNIT 8 ++ ++#define MP2975_PSC_VOLTAGE_OUT 0x40 ++#define MP2975_MAX_PHASE_RAIL1 8 ++#define MP2975_MAX_PHASE_RAIL2 4 ++#define MP2975_PAGE_NUM 2 ++ ++#define MP2975_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | \ ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT) ++ ++struct mp2975_data { ++ struct pmbus_driver_info info; ++ int vout_scale; ++ int vid_step[MP2975_PAGE_NUM]; ++ int vref[MP2975_PAGE_NUM]; ++ int vref_off[MP2975_PAGE_NUM]; ++ int vout_max[MP2975_PAGE_NUM]; ++ int vout_ov_fixed[MP2975_PAGE_NUM]; ++ int vout_format[MP2975_PAGE_NUM]; ++ int curr_sense_gain[MP2975_PAGE_NUM]; ++}; ++ ++#define to_mp2975_data(x) container_of(x, struct mp2975_data, info) ++ ++static int mp2975_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ /* ++ * Enforce VOUT direct format, since device allows to set the ++ * different formats for the different rails. Conversion from ++ * VID to direct provided by driver internally, in case it is ++ * necessary. ++ */ ++ return MP2975_PSC_VOLTAGE_OUT; ++ default: ++ return -ENODATA; ++ } ++} ++ ++static int ++mp2975_read_word_helper(struct i2c_client *client, int page, int phase, u8 reg, ++ u16 mask) ++{ ++ int ret = pmbus_read_word_data(client, page, reg); ++ ++ return (ret > 0) ? ret & mask : ret; ++} ++ ++static int ++mp2975_vid2direct(int vrf, int val) ++{ ++ switch (vrf) { ++ case vr12: ++ if (val >= 0x01) ++ return 250 + (val - 1) * 5; ++ break; ++ case vr13: ++ if (val >= 0x01) ++ return 500 + (val - 1) * 10; ++ break; ++ case imvp9: ++ if (val >= 0x01) ++ return 200 + (val - 1) * 10; ++ break; ++ default: ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static int ++mp2975_read_phase(struct i2c_client *client, struct mp2975_data *data, ++ int page, int phase, u8 reg) ++{ ++ u16 mask; ++ int shift = 0, ret; ++ ++ if ((phase + 1) % MP2975_PAGE_NUM) { ++ mask = GENMASK(7, 0); ++ } else { ++ mask = GENMASK(15, 8); ++ shift = 8; ++ } ++ ++ ret = mp2975_read_word_helper(client, page, phase, reg, mask); ++ if (ret < 0) ++ return ret; ++ ++ ret >>= shift; ++ ++ /* ++ * Output value is calculated as: (READ_CSx / 80 – 1.23) / (Kcs * Rcs) ++ * where: ++ * - Kcs is the DrMOS current sense gain of power stage, which is ++ * obtained from the register MP2975_MFR_VR_CONFIG1, bits 13-12 with ++ * the following selection of DrMOS (data->curr_sense_gain[page]): ++ * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. ++ * - Rcs is the internal phase current sense resistor which is constant ++ * value 1kΩ. ++ */ ++ return DIV_ROUND_CLOSEST(DIV_ROUND_CLOSEST(ret * 100 - 9840, 100) * ++ 100, data->curr_sense_gain[page]); ++} ++ ++static int ++mp2975_read_phases(struct i2c_client *client, struct mp2975_data *data, ++ int page, int phase) ++{ ++ int ret; ++ ++ if (page) { ++ switch (phase) { ++ case 0 ... 1: ++ ret = mp2975_read_phase(client, data, page, phase, ++ MP2975_MFR_READ_CS7_8); ++ break; ++ case 2 ... 3: ++ ret = mp2975_read_phase(client, data, page, phase, ++ MP2975_MFR_READ_CS9_10); ++ break; ++ case 4 ... 5: ++ ret = mp2975_read_phase(client, data, page, phase, ++ MP2975_MFR_READ_CS11_12); ++ break; ++ default: ++ return -ENODATA; ++ } ++ } else { ++ switch (phase) { ++ case 0 ... 1: ++ ret = mp2975_read_phase(client, data, page, phase, ++ MP2975_MFR_READ_CS1_2); ++ break; ++ case 2 ... 3: ++ ret = mp2975_read_phase(client, data, page, phase, ++ MP2975_MFR_READ_CS3_4); ++ break; ++ case 4 ... 5: ++ ret = mp2975_read_phase(client, data, page, phase, ++ MP2975_MFR_READ_CS5_6); ++ break; ++ case 6 ... 7: ++ ret = mp2975_read_phase(client, data, page, phase, ++ MP2975_MFR_READ_CS7_8); ++ break; ++ case 8 ... 9: ++ ret = mp2975_read_phase(client, data, page, phase, ++ MP2975_MFR_READ_CS9_10); ++ break; ++ case 10 ... 11: ++ ret = mp2975_read_phase(client, data, page, phase, ++ MP2975_MFR_READ_CS11_12); ++ break; ++ default: ++ return -ENODATA; ++ } ++ } ++ return ret; ++} ++ ++static int mp2975_read_word_data(struct i2c_client *client, int page, ++ int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mp2975_data *data = to_mp2975_data(info); ++ int phase = 255; ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_OT_FAULT_LIMIT: ++ ret = mp2975_read_word_helper(client, page, phase, reg, ++ GENMASK(7, 0)); ++ break; ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ ret = mp2975_read_word_helper(client, page, phase, reg, ++ GENMASK(7, 0)); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST(ret, MP2975_VIN_UV_LIMIT_UNIT); ++ break; ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ /* ++ * Register provides two values for over-voltage protection ++ * threshold for fixed (ovp2) and tracking (ovp1) modes. The ++ * minimum of these two values is provided as over-voltage ++ * fault alarm. ++ */ ++ ret = mp2975_read_word_helper(client, page, phase, ++ MP2975_MFR_OVP_TH_SET, ++ GENMASK(2, 0)); ++ if (ret < 0) ++ return ret; ++ ++ ret = min_t(int, data->vout_max[page] + 50 * (ret + 1), ++ data->vout_ov_fixed[page]); ++ break; ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ ret = mp2975_read_word_helper(client, page, phase, ++ MP2975_MFR_UVP_SET, ++ GENMASK(2, 0)); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST(data->vref[page] * 10 - 50 * ++ (ret + 1) * data->vout_scale, 10); ++ break; ++ case PMBUS_READ_VOUT: ++ ret = mp2975_read_word_helper(client, page, phase, reg, ++ GENMASK(11, 0)); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * READ_VOUT can be provided in VID or direct format. The ++ * format type is specified by bit 15 of the register ++ * MP2975_MFR_DC_LOOP_CTRL. The driver enforces VOUT direct ++ * format, since device allows to set the different formats for ++ * the different rails and also all VOUT limits registers are ++ * provided in a direct format. In case format is VID - convert ++ * to direct. ++ */ ++ if (data->vout_format[page] == vid) ++ ret = mp2975_vid2direct(info->vrm_version[page], ret); ++ break; ++ case PMBUS_VIRT_READ_POUT_MAX: ++ ret = mp2975_read_word_helper(client, page, phase, ++ MP2975_MFR_READ_POUT_PK, ++ GENMASK(12, 0)); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST(ret, 4); ++ break; ++ case PMBUS_VIRT_READ_IOUT_MAX: ++ ret = mp2975_read_word_helper(client, page, phase, ++ MP2975_MFR_READ_IOUT_PK, ++ GENMASK(12, 0)); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST(ret, 4); ++ break; ++ case PMBUS_READ_IOUT: ++ ret = mp2975_read_phases(client, data, page, phase); ++ if (ret < 0) ++ return ret; ++ ++ break; ++ case PMBUS_UT_WARN_LIMIT: ++ case PMBUS_UT_FAULT_LIMIT: ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_LV_FAULT_LIMIT: ++ case PMBUS_IIN_OC_WARN_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_UC_FAULT_LIMIT: ++ case PMBUS_POUT_OP_FAULT_LIMIT: ++ case PMBUS_POUT_OP_WARN_LIMIT: ++ case PMBUS_PIN_OP_WARN_LIMIT: ++ return -ENXIO; ++ default: ++ return -ENODATA; ++ } ++ ++ return ret; ++} ++ ++static int mp2975_identify_multiphase_rail2(struct i2c_client *client) ++{ ++ int ret; ++ ++ /* ++ * Identify multiphase for rail 2 - could be from 0 to 4. ++ * In case phase number is zero – only page zero is supported ++ */ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify multiphase for rail 2 - could be from 0 to 4. */ ++ ret = i2c_smbus_read_word_data(client, MP2975_MFR_VR_MULTI_CONFIG_R2); ++ if (ret < 0) ++ return ret; ++ ++ ret &= GENMASK(2, 0); ++ return (ret >= 4) ? 4 : ret; ++} ++#if 0 ++static void mp2975_set_phase_rail1(struct pmbus_driver_info *info) ++{ ++ int i; ++ ++ for (i = 0 ; i < info->phases[0]; i++) ++ info->pfunc[i] = PMBUS_HAVE_IOUT; ++} ++ ++static void mp2975_set_phase_rail2(struct pmbus_driver_info *info) ++{ ++ int max_rail, i; ++ ++ /* Set phases for rail 2 from upper to lower. */ ++ max_rail = info->phases[1] % (MP2975_MAX_PHASE_RAIL2 - 1); ++ for (i = 1 ; i <= max_rail; i++) ++ info->pfunc[MP2975_MAX_PHASE_RAIL1 - i] = PMBUS_HAVE_IOUT; ++} ++ ++static int mp2975_set_multiphase_rail2(struct pmbus_driver_info *info) ++{ ++ switch (info->phases[1]) { ++ case 1 ... 7: ++ mp2975_set_phase_rail2(info); ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int ++mp2975_identify_multiphase(struct i2c_client *client, struct mp2975_data *data, ++ struct pmbus_driver_info *info) ++{ ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify multiphase for rail 1 - could be from 1 to 8. */ ++ ret = i2c_smbus_read_word_data(client, MP2975_MFR_VR_MULTI_CONFIG_R1); ++ if (ret > 0) ++ info->phases[0] = ret & GENMASK(3, 0); ++ else ++ return (ret) ? ret : -EINVAL; ++ ++ /* ++ * The device provides a total of 8 PWM pins, and can be configured ++ * to different phase count applications for rail 1 and rail 2. ++ * Rail 1 can be set to 8 phases, while rail 2 can only be set to 4 ++ * phases at most. When rail 1’s phase count is configured as 0, rail ++ * 1 operates with 1-phase DCM. When rail 2 phase count is configured ++ * as 0, rail 2 is disabled. ++ */ ++ switch (info->phases[0]) { ++ case 1 ... 4: ++ mp2975_set_phase_rail1(info); ++ return mp2975_set_multiphase_rail2(info); ++ case 5: ++ mp2975_set_phase_rail1(info); ++ switch (info->phases[1]) { ++ case 1 ... 3: ++ return mp2975_set_multiphase_rail2(info); ++ default: ++ return 0; ++ } ++ case 6: ++ mp2975_set_phase_rail1(info); ++ switch (info->phases[1]) { ++ case 1 ... 2: ++ return mp2975_set_multiphase_rail2(info); ++ default: ++ return 0; ++ } ++ case 7: ++ mp2975_set_phase_rail1(info); ++ switch (info->phases[1]) { ++ case 1: ++ return mp2975_set_multiphase_rail2(info); ++ default: ++ return 0; ++ } ++ case 8: ++ mp2975_set_phase_rail1(info); ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++#endif ++static int ++mp2975_identify_vid(struct i2c_client *client, struct mp2975_data *data, ++ struct pmbus_driver_info *info, u32 reg, int page, ++ u32 imvp_bit, u32 vr_bit) ++{ ++ int ret; ++ ++ /* Identify VID mode and step selection. */ ++ ret = i2c_smbus_read_word_data(client, reg); ++ if (ret < 0) ++ return ret; ++ ++ if (ret & imvp_bit) { ++ info->vrm_version[page] = imvp9; ++ data->vid_step[page] = MP2975_PROT_DEV_OV_OFF; ++ } else if (ret & vr_bit) { ++ info->vrm_version[page] = vr12; ++ data->vid_step[page] = MP2975_PROT_DEV_OV_ON; ++ } else { ++ info->vrm_version[page] = vr13; ++ data->vid_step[page] = MP2975_PROT_DEV_OV_OFF; ++ } ++ ++ return 0; ++} ++ ++static int ++mp2975_identify_rails_vid(struct i2c_client *client, struct mp2975_data *data, ++ struct pmbus_driver_info *info) ++{ ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify VID mode for rail 1. */ ++ ret = mp2975_identify_vid(client, data, info, ++ MP2975_MFR_VR_MULTI_CONFIG_R1, 0, ++ MP2975_IMVP9_EN_R1, MP2975_VID_STEP_SEL_R1); ++ if (ret < 0) ++ return ret; ++ ++ /* Identify VID mode for rail 2, if connected. */ ++ if (info->pages == MP2975_PAGE_NUM) ++ ret = mp2975_identify_vid(client, data, info, ++ MP2975_MFR_VR_MULTI_CONFIG_R2, 1, ++ MP2975_IMVP9_EN_R2, ++ MP2975_VID_STEP_SEL_R2); ++ return ret; ++} ++ ++static int ++mp2975_current_sense_gain_get(struct i2c_client *client, ++ struct mp2975_data *data) ++{ ++ int i, ret; ++ ++ /* ++ * Obtain DrMOS current sense gain of power stage from the register ++ * MP2975_MFR_VR_CONFIG1, bits 13-12. The value is selected as below: ++ * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. Other ++ * values are invalid. ++ */ ++ for (i = 0 ; i < data->info.pages; i++) { ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); ++ if (ret < 0) ++ return ret; ++ ret = i2c_smbus_read_word_data(client, ++ MP2975_MFR_VR_CONFIG1); ++ if (ret < 0) ++ return ret; ++ ++ switch ((ret & MP2975_DRMOS_KCS) >> 12) { ++ case 0: ++ data->curr_sense_gain[i] = 50; ++ break; ++ case 1: ++ data->curr_sense_gain[i] = 85; ++ break; ++ case 2: ++ data->curr_sense_gain[i] = 97; ++ break; ++ default: ++ data->curr_sense_gain[i] = 100; ++ break; ++ } ++ } ++ ++ return 0; ++} ++ ++static int ++mp2975_vref_get(struct i2c_client *client, struct mp2975_data *data, ++ struct pmbus_driver_info *info) ++{ ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 3); ++ if (ret < 0) ++ return ret; ++ ++ /* Get voltage reference value for rail 1. */ ++ ret = i2c_smbus_read_word_data(client, MP2975_MFR_READ_VREF_R1); ++ if (ret < 0) ++ return ret; ++ ++ data->vref[0] = ret * data->vid_step[0]; ++ ++ /* Get voltage reference value for rail 2, if connected. */ ++ if (data->info.pages == MP2975_PAGE_NUM) { ++ ret = i2c_smbus_read_word_data(client, MP2975_MFR_READ_VREF_R2); ++ if (ret < 0) ++ return ret; ++ ++ data->vref[1] = ret * data->vid_step[1]; ++ } ++ return 0; ++} ++ ++static int ++mp2975_vref_offset_get(struct i2c_client *client, struct mp2975_data *data, ++ int page) ++{ ++ int ret; ++ ++ ret = i2c_smbus_read_word_data(client, MP2975_MFR_OVP_TH_SET); ++ if (ret < 0) ++ return ret; ++ ++ switch ((ret & GENMASK(5, 3)) >> 3) { ++ case 1: ++ data->vref_off[page] = 140; ++ break; ++ case 2: ++ data->vref_off[page] = 220; ++ break; ++ case 4: ++ data->vref_off[page] = 400; ++ break; ++ default: ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static int ++mp2975_vout_max_get(struct i2c_client *client, struct mp2975_data *data, ++ struct pmbus_driver_info *info, int page) ++{ ++ int ret; ++ ++ /* Get maximum reference voltage of VID-DAC in VID format. */ ++ ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_MAX); ++ if (ret < 0) ++ return ret; ++ ++ data->vout_max[page] = mp2975_vid2direct(info->vrm_version[page], ret & ++ GENMASK(8, 0)); ++ return 0; ++} ++ ++static int ++mp2975_identify_vout_format(struct i2c_client *client, ++ struct mp2975_data *data, int page) ++{ ++ int ret; ++ ++ ret = i2c_smbus_read_word_data(client, MP2975_MFR_DC_LOOP_CTRL); ++ if (ret < 0) ++ return ret; ++ ++ if (ret & MP2975_VOUT_FORMAT) ++ data->vout_format[page] = vid; ++ else ++ data->vout_format[page] = direct; ++ return 0; ++} ++ ++static int ++mp2975_vout_ov_scale_get(struct i2c_client *client, struct mp2975_data *data, ++ struct pmbus_driver_info *info) ++{ ++ int thres_dev, sense_ampl, ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Get divider for over- and under-voltage protection thresholds ++ * configuration from the Advanced Options of Auto Phase Shedding and ++ * decay register. ++ */ ++ ret = i2c_smbus_read_word_data(client, MP2975_MFR_APS_DECAY_ADV); ++ if (ret < 0) ++ return ret; ++ thres_dev = ret & MP2975_PRT_THRES_DIV_OV_EN ? MP2975_PROT_DEV_OV_ON : ++ MP2975_PROT_DEV_OV_OFF; ++ ++ /* Select the gain of remote sense amplifier. */ ++ ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_SCALE_LOOP); ++ if (ret < 0) ++ return ret; ++ sense_ampl = ret & MP2975_SENSE_AMPL ? MP2975_SENSE_AMPL_HALF : ++ MP2975_SENSE_AMPL_UNIT; ++ ++ data->vout_scale = sense_ampl * thres_dev; ++ ++ return 0; ++} ++ ++static int ++mp2975_vout_per_rail_config_get(struct i2c_client *client, ++ struct mp2975_data *data, ++ struct pmbus_driver_info *info) ++{ ++ int i, ret; ++ ++ for (i = 0; i < data->info.pages; i++) { ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); ++ if (ret < 0) ++ return ret; ++ ++ /* Obtain voltage reference offsets. */ ++ ret = mp2975_vref_offset_get(client, data, i); ++ if (ret < 0) ++ return ret; ++ ++ /* Obtain maximum voltage values. */ ++ ret = mp2975_vout_max_get(client, data, info, i); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Get VOUT format for READ_VOUT command : VID or direct. ++ * Pages on same device can be configured with different ++ * formats. ++ */ ++ ret = mp2975_identify_vout_format(client, data, i); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Set over-voltage fixed value. Thresholds are provided as ++ * fixed value, and tracking value. The minimum of them are ++ * exposed as over-voltage critical threshold. ++ */ ++ data->vout_ov_fixed[i] = data->vref[i] + ++ DIV_ROUND_CLOSEST(data->vref_off[i] * ++ data->vout_scale, ++ 10); ++ } ++ ++ return 0; ++} ++ ++static struct pmbus_driver_info mp2975_info = { ++ .pages = 1, ++ .format[PSC_VOLTAGE_IN] = linear, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .format[PSC_CURRENT_IN] = linear, ++ .format[PSC_CURRENT_OUT] = direct, ++ .format[PSC_POWER] = direct, ++ .m[PSC_TEMPERATURE] = 1, ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .m[PSC_CURRENT_OUT] = 1, ++ .m[PSC_POWER] = 1, ++ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT | ++ PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, ++ .read_byte_data = mp2975_read_byte_data, ++ .read_word_data = mp2975_read_word_data, ++}; ++ ++static int mp2975_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ struct pmbus_driver_info *info; ++ struct mp2975_data *data; ++ int ret; ++ ++ data = devm_kzalloc(&client->dev, sizeof(struct mp2975_data), ++ GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ memcpy(&data->info, &mp2975_info, sizeof(*info)); ++ info = &data->info; ++ ++ /* Identify multiphase configuration for rail 2. */ ++ ret = mp2975_identify_multiphase_rail2(client); ++ if (ret < 0) ++ return ret; ++ ++ if (ret) ++ data->info.pages = MP2975_PAGE_NUM; ++ ++ if (ret) { ++ /* Two rails are connected. */ ++ data->info.pages = MP2975_PAGE_NUM; ++#if 0 ++ data->info.phases[1] = ret; ++#endif ++ data->info.func[1] = MP2975_RAIL2_FUNC; ++ } ++#if 0 ++ /* Identify multiphase configuration. */ ++ ret = mp2975_identify_multiphase(client, data, info); ++ if (ret) ++ return ret; ++#endif ++ /* Identify VID setting per rail. */ ++ ret = mp2975_identify_rails_vid(client, data, info); ++ if (ret < 0) ++ return ret; ++ ++ /* Obtain current sense gain of power stage. */ ++ ret = mp2975_current_sense_gain_get(client, data); ++ if (ret) ++ return ret; ++ ++ /* Obtain voltage reference values. */ ++ ret = mp2975_vref_get(client, data, info); ++ if (ret) ++ return ret; ++ ++ /* Obtain vout over-voltage scales. */ ++ ret = mp2975_vout_ov_scale_get(client, data, info); ++ if (ret < 0) ++ return ret; ++ ++ /* Obtain offsets, maximum and format for vout. */ ++ ret = mp2975_vout_per_rail_config_get(client, data, info); ++ if (ret) ++ return ret; ++ ++ return pmbus_do_probe(client, id, info); ++} ++ ++static const struct i2c_device_id mp2975_id[] = { ++ {"mp2975", 0}, ++ {} ++}; ++ ++MODULE_DEVICE_TABLE(i2c, mp2975_id); ++ ++static const struct of_device_id __maybe_unused mp2975_of_match[] = { ++ {.compatible = "mps,mp2975"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mp2975_of_match); ++ ++static struct i2c_driver mp2975_driver = { ++ .driver = { ++ .name = "mp2975", ++ .of_match_table = of_match_ptr(mp2975_of_match), ++ }, ++ .probe = mp2975_probe, ++ .remove = pmbus_do_remove, ++ .id_table = mp2975_id, ++}; ++ ++module_i2c_driver(mp2975_driver); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("PMBus driver for MPS MP2975 device"); ++MODULE_LICENSE("GPL"); +-- +2.8.4 + diff --git a/patch/0030-hwmon-Add-convience-macro-to-define-simple-static-se.patch b/patch/0030-hwmon-Add-convience-macro-to-define-simple-static-se.patch new file mode 100644 index 000000000000..b96b95fb35e6 --- /dev/null +++ b/patch/0030-hwmon-Add-convience-macro-to-define-simple-static-se.patch @@ -0,0 +1,39 @@ +From b33335cbc89357b415865e7c55a4103bbd47f629 Mon Sep 17 00:00:00 2001 +From: Charles Keepax +Date: Wed, 20 Mar 2019 14:58:17 +0000 +Subject: [PATCH 1/2] hwmon: Add convience macro to define simple static + sensors + +cherry-picked from upstream c43a113ca2c807c3e66a5de0ec57d69803b8bc10 + +It takes a fair amount of boiler plate code to add new sensors, add a +macro that can be used to specify simple static sensors. + +Signed-off-by: Charles Keepax +Signed-off-by: Guenter Roeck +--- + include/linux/hwmon.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h +index 9493d4a..faa67641 100644 +--- a/include/linux/hwmon.h ++++ b/include/linux/hwmon.h +@@ -363,6 +363,14 @@ struct hwmon_channel_info { + const u32 *config; + }; + ++#define HWMON_CHANNEL_INFO(stype, ...) \ ++ (&(struct hwmon_channel_info) { \ ++ .type = hwmon_##stype, \ ++ .config = (u32 []) { \ ++ __VA_ARGS__, 0 \ ++ } \ ++ }) ++ + /** + * Chip configuration + * @ops: Pointer to hwmon operations. +-- +2.8.4 + diff --git a/patch/0031-backport-nvme-Add-hardware-monitoring-support.patch b/patch/0031-backport-nvme-Add-hardware-monitoring-support.patch new file mode 100644 index 000000000000..39084814b662 --- /dev/null +++ b/patch/0031-backport-nvme-Add-hardware-monitoring-support.patch @@ -0,0 +1,314 @@ +From 430612584b9a4904783d767d32cf65c809743796 Mon Sep 17 00:00:00 2001 +From: Guenter Roeck +Date: Wed, 6 Nov 2019 06:35:18 -0800 +Subject: [PATCH 2/2] backport: nvme: Add hardware monitoring support +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +backport from upstream commit 400b6a7b13a3fd71cff087139ce45dd1e5fff444 + +nvme devices report temperature information in the controller information +(for limits) and in the smart log. Currently, the only means to retrieve +this information is the nvme command line interface, which requires +super-user privileges. + +At the same time, it would be desirable to be able to use NVMe temperature +information for thermal control. + +This patch adds support to read NVMe temperatures from the kernel using the +hwmon API and adds temperature zones for NVMe drives. The thermal subsystem +can use this information to set thermal policies, and userspace can access +it using libsensors and/or the "sensors" command. + +Example output from the "sensors" command: + +nvme0-pci-0100 +Adapter: PCI adapter +Composite: +39.0°C (high = +85.0°C, crit = +85.0°C) +Sensor 1: +39.0°C +Sensor 2: +41.0°C + +Reviewed-by: Christoph Hellwig +Signed-off-by: Guenter Roeck +Signed-off-by: Keith Busch +--- + drivers/nvme/host/Kconfig | 10 +++ + drivers/nvme/host/Makefile | 1 + + drivers/nvme/host/core.c | 6 ++ + drivers/nvme/host/hwmon.c | 181 +++++++++++++++++++++++++++++++++++++++++++++ + drivers/nvme/host/nvme.h | 8 ++ + 5 files changed, 206 insertions(+) + create mode 100644 drivers/nvme/host/hwmon.c + +diff --git a/drivers/nvme/host/Kconfig b/drivers/nvme/host/Kconfig +index 88a8b59..72ddd22 100644 +--- a/drivers/nvme/host/Kconfig ++++ b/drivers/nvme/host/Kconfig +@@ -22,6 +22,16 @@ config NVME_MULTIPATH + /dev/nvmeXnY device will show up for each NVMe namespaces, + even if it is accessible through multiple controllers. + ++config NVME_HWMON ++ bool "NVMe hardware monitoring" ++ depends on (NVME_CORE=y && HWMON=y) || (NVME_CORE=m && HWMON) ++ help ++ This provides support for NVMe hardware monitoring. If enabled, ++ a hardware monitoring device will be created for each NVMe drive ++ in the system. ++ ++ If unsure, say N. ++ + config NVME_FABRICS + tristate + +diff --git a/drivers/nvme/host/Makefile b/drivers/nvme/host/Makefile +index aea459c..525ff99 100644 +--- a/drivers/nvme/host/Makefile ++++ b/drivers/nvme/host/Makefile +@@ -13,6 +13,7 @@ nvme-core-$(CONFIG_TRACING) += trace.o + nvme-core-$(CONFIG_NVME_MULTIPATH) += multipath.o + nvme-core-$(CONFIG_NVM) += lightnvm.o + nvme-core-$(CONFIG_FAULT_INJECTION_DEBUG_FS) += fault_inject.o ++nvme-core-$(CONFIG_NVME_HWMON) += hwmon.o + + nvme-y += pci.o + +diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c +index e26d119..bb37aae 100644 +--- a/drivers/nvme/host/core.c ++++ b/drivers/nvme/host/core.c +@@ -2432,6 +2432,9 @@ int nvme_init_identify(struct nvme_ctrl *ctrl) + ctrl->oacs = le16_to_cpu(id->oacs); + ctrl->oncs = le16_to_cpup(&id->oncs); + ctrl->oaes = le32_to_cpu(id->oaes); ++ ctrl->wctemp = le16_to_cpu(id->wctemp); ++ ctrl->cctemp = le16_to_cpu(id->cctemp); ++ + atomic_set(&ctrl->abort_limit, id->acl + 1); + ctrl->vwc = id->vwc; + ctrl->cntlid = le16_to_cpup(&id->cntlid); +@@ -2528,6 +2531,9 @@ int nvme_init_identify(struct nvme_ctrl *ctrl) + if (ret < 0) + return ret; + ++ if (!ctrl->identified) ++ nvme_hwmon_init(ctrl); ++ + ctrl->identified = true; + + return 0; +diff --git a/drivers/nvme/host/hwmon.c b/drivers/nvme/host/hwmon.c +new file mode 100644 +index 0000000..5480cbb +--- /dev/null ++++ b/drivers/nvme/host/hwmon.c +@@ -0,0 +1,181 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * NVM Express hardware monitoring support ++ * Copyright (c) 2019, Guenter Roeck ++ */ ++ ++#include ++#include ++ ++#include "nvme.h" ++ ++struct nvme_hwmon_data { ++ struct nvme_ctrl *ctrl; ++ struct nvme_smart_log log; ++ struct mutex read_lock; ++}; ++ ++static int nvme_hwmon_get_smart_log(struct nvme_hwmon_data *data) ++{ ++ int ret; ++ ++ ret = nvme_get_log(data->ctrl, NVME_NSID_ALL, NVME_LOG_SMART, 0, ++ &data->log, sizeof(data->log), 0); ++ ++ return ret <= 0 ? ret : -EIO; ++} ++ ++static int nvme_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct nvme_hwmon_data *data = dev_get_drvdata(dev); ++ struct nvme_smart_log *log = &data->log; ++ int temp; ++ int err; ++ ++ /* ++ * First handle attributes which don't require us to read ++ * the smart log. ++ */ ++ switch (attr) { ++ case hwmon_temp_max: ++ *val = (data->ctrl->wctemp - 273) * 1000; ++ return 0; ++ case hwmon_temp_crit: ++ *val = (data->ctrl->cctemp - 273) * 1000; ++ return 0; ++ default: ++ break; ++ } ++ ++ mutex_lock(&data->read_lock); ++ err = nvme_hwmon_get_smart_log(data); ++ if (err) ++ goto unlock; ++ ++ switch (attr) { ++ case hwmon_temp_input: ++ if (!channel) ++ temp = get_unaligned_le16(log->temperature); ++ else ++ temp = le16_to_cpu(log->temp_sensor[channel - 1]); ++ *val = (temp - 273) * 1000; ++ break; ++ case hwmon_temp_alarm: ++ *val = !!(log->critical_warning & NVME_SMART_CRIT_TEMPERATURE); ++ break; ++ default: ++ err = -EOPNOTSUPP; ++ break; ++ } ++unlock: ++ mutex_unlock(&data->read_lock); ++ return err; ++} ++ ++static const char * const nvme_hwmon_sensor_names[] = { ++ "Composite", ++ "Sensor 1", ++ "Sensor 2", ++ "Sensor 3", ++ "Sensor 4", ++ "Sensor 5", ++ "Sensor 6", ++ "Sensor 7", ++ "Sensor 8", ++}; ++ ++static int nvme_hwmon_read_string(struct device *dev, ++ enum hwmon_sensor_types type, u32 attr, ++ int channel, const char **str) ++{ ++ *str = nvme_hwmon_sensor_names[channel]; ++ return 0; ++} ++ ++static umode_t nvme_hwmon_is_visible(const void *_data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ const struct nvme_hwmon_data *data = _data; ++ ++ switch (attr) { ++ case hwmon_temp_crit: ++ if (!channel && data->ctrl->cctemp) ++ return 0444; ++ break; ++ case hwmon_temp_max: ++ if (!channel && data->ctrl->wctemp) ++ return 0444; ++ break; ++ case hwmon_temp_alarm: ++ if (!channel) ++ return 0444; ++ break; ++ case hwmon_temp_input: ++ case hwmon_temp_label: ++ if (!channel || data->log.temp_sensor[channel - 1]) ++ return 0444; ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++static const struct hwmon_channel_info *nvme_hwmon_info[] = { ++ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), ++ HWMON_CHANNEL_INFO(temp, ++ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | ++ HWMON_T_LABEL | HWMON_T_ALARM, ++ 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), ++ NULL ++}; ++ ++static const struct hwmon_ops nvme_hwmon_ops = { ++ .is_visible = nvme_hwmon_is_visible, ++ .read = nvme_hwmon_read, ++ .read_string = nvme_hwmon_read_string, ++}; ++ ++static const struct hwmon_chip_info nvme_hwmon_chip_info = { ++ .ops = &nvme_hwmon_ops, ++ .info = nvme_hwmon_info, ++}; ++ ++void nvme_hwmon_init(struct nvme_ctrl *ctrl) ++{ ++ struct device *dev = ctrl->dev; ++ struct nvme_hwmon_data *data; ++ struct device *hwmon; ++ int err; ++ ++ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return; ++ ++ data->ctrl = ctrl; ++ mutex_init(&data->read_lock); ++ ++ err = nvme_hwmon_get_smart_log(data); ++ if (err) { ++ dev_warn(dev, "Failed to read smart log (error %d)\n", err); ++ devm_kfree(dev, data); ++ return; ++ } ++ ++ hwmon = devm_hwmon_device_register_with_info(dev, "nvme", data, ++ &nvme_hwmon_chip_info, ++ NULL); ++ if (IS_ERR(hwmon)) { ++ dev_warn(dev, "Failed to instantiate hwmon device\n"); ++ devm_kfree(dev, data); ++ } ++} +diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h +index d5e29b5..029bc8a 100644 +--- a/drivers/nvme/host/nvme.h ++++ b/drivers/nvme/host/nvme.h +@@ -192,6 +192,8 @@ struct nvme_ctrl { + u16 kas; + u8 npss; + u8 apsta; ++ u16 wctemp; ++ u16 cctemp; + u32 oaes; + u32 aen_result; + unsigned int shutdown_timeout; +@@ -591,4 +593,10 @@ static inline struct nvme_ns *nvme_get_ns_from_dev(struct device *dev) + int __init nvme_core_init(void); + void nvme_core_exit(void); + ++#ifdef CONFIG_NVME_HWMON ++void nvme_hwmon_init(struct nvme_ctrl *ctrl); ++#else ++static inline void nvme_hwmon_init(struct nvme_ctrl *ctrl) { } ++#endif ++ + #endif /* _NVME_H */ +-- +2.8.4 + diff --git a/patch/0032-platform-mellanox-mlxreg-hotplug-Use-capability-regi.patch b/patch/0032-platform-mellanox-mlxreg-hotplug-Use-capability-regi.patch new file mode 100644 index 000000000000..c156d81736f0 --- /dev/null +++ b/patch/0032-platform-mellanox-mlxreg-hotplug-Use-capability-regi.patch @@ -0,0 +1,112 @@ +From 9f37a3899fae02ee41185b16f124116819c56fc6 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Mon, 8 Feb 2021 00:45:47 +0200 +Subject: [PATCH backport 4.19 06/10] platform/mellanox: mlxreg-hotplug: Use + capability register for attribute creation + +Align with upstream commit 0a43f7be57edbe9fac6084a6f92694a3badee827. + +Create the 'sysfs' attributes according to configuration provided +through the capability register, which purpose is to indicate the +actual number of the components within the particular group. +Such components could be, for example the FAN or power supply units. +The motivation is to avoid adding a new code in the future in order to +distinct between the systems types supporting a different number of the +components like power supplies, FANs, ASICs, line cards. + +Signed-off-by: Vadim Pasternak +Signed-off-by: Andy Shevchenko +--- + drivers/platform/mellanox/mlxreg-hotplug.c | 48 ++++++++---------------------- + 1 file changed, 13 insertions(+), 35 deletions(-) + +diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c +index 3e99ad9fe553..ed888d719024 100644 +--- a/drivers/platform/mellanox/mlxreg-hotplug.c ++++ b/drivers/platform/mellanox/mlxreg-hotplug.c +@@ -1,34 +1,8 @@ ++// SPDX-License-Identifier: GPL-2.0+ + /* +- * Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved. +- * Copyright (c) 2016-2018 Vadim Pasternak ++ * Mellanox hotplug driver + * +- * Redistribution and use in source and binary forms, with or without +- * modification, are permitted provided that the following conditions are met: +- * +- * 1. Redistributions of source code must retain the above copyright +- * notice, this list of conditions and the following disclaimer. +- * 2. Redistributions in binary form must reproduce the above copyright +- * notice, this list of conditions and the following disclaimer in the +- * documentation and/or other materials provided with the distribution. +- * 3. Neither the names of the copyright holders nor the names of its +- * contributors may be used to endorse or promote products derived from +- * this software without specific prior written permission. +- * +- * Alternatively, this software may be distributed under the terms of the +- * GNU General Public License ("GPL") version 2 as published by the Free +- * Software Foundation. +- * +- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +- * POSSIBILITY OF SUCH DAMAGE. ++ * Copyright (C) 2016-2020 Mellanox Technologies + */ + + #include +@@ -220,6 +194,7 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) + struct mlxreg_core_hotplug_platform_data *pdata; + struct mlxreg_core_item *item; + struct mlxreg_core_data *data; ++ unsigned long mask; + u32 regval; + int num_attrs = 0, id = 0, i, j, k, ret; + +@@ -243,11 +218,11 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) + } + + data = item->data; +- /* Go over all units within the item. */ +- for (j = 0, k = 0; j < item->count; j++, data++) { +- /* Skip if bit in mask is not set. */ +- if (!(item->mask & BIT(j))) +- continue; ++ ++ /* Go over all unmasked units within item. */ ++ mask = item->mask; ++ k = 0; ++ for_each_set_bit(j, &mask, item->count) { + if (data->capability) { + /* + * Read capability register and skip non +@@ -257,8 +232,10 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) + data->capability, ®val); + if (ret) + return ret; +- if (!(regval & data->bit)) ++ if (!(regval & data->bit)) { ++ data++; + continue; ++ } + } + PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr; + PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev, +@@ -279,6 +256,7 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) + PRIV_DEV_ATTR(id).nr = i; + PRIV_DEV_ATTR(id).index = k; + sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr); ++ data++; + id++; + k++; + } +-- +2.11.0 + diff --git a/patch/kconfig-inclusions b/patch/kconfig-inclusions index c7fb4de6c5f3..e57063b674fc 100644 --- a/patch/kconfig-inclusions +++ b/patch/kconfig-inclusions @@ -46,6 +46,7 @@ CONFIG_SENSORS_TPS53679=m CONFIG_SENSORS_UCD9000=m CONFIG_SENSORS_UCD9200=m CONFIG_SENSORS_XDPE122=m +CONFIG_SENSORS_MP2975=m CONFIG_MLX_WDT=y CONFIG_LEDS_MLXREG=m CONFIG_DW_DMAC_PCI=m diff --git a/patch/series b/patch/series index d71804434a7d..f7b1a4982ddb 100755 --- a/patch/series +++ b/patch/series @@ -77,6 +77,11 @@ driver-ixgbe-external-phy.patch 0025-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch 0026-platform-x86-mlx-platform-Remove-PSU-EEPROM-configur.patch 0027-mlxsw-core-Remove-critical-trip-point-from-thermal-z.patch +0028-thermal-Fix-deadlock-in-thermal-thermal_zone_device_.patch +0029-hwmon-pmbus-Add-support-for-MPS-Multi-phase-mp2975-c.patch +0030-hwmon-Add-convience-macro-to-define-simple-static-se.patch +0031-backport-nvme-Add-hardware-monitoring-support.patch +0032-platform-mellanox-mlxreg-hotplug-Use-capability-regi.patch ############################################################ # # Internal patches will be added below (placeholder)