diff options
Diffstat (limited to 'drivers/thermal/intel')
15 files changed, 824 insertions, 223 deletions
diff --git a/drivers/thermal/intel/int340x_thermal/Makefile b/drivers/thermal/intel/int340x_thermal/Makefile index 4e852ce4a5..fe3f439245 100644 --- a/drivers/thermal/intel/int340x_thermal/Makefile +++ b/drivers/thermal/intel/int340x_thermal/Makefile @@ -10,5 +10,8 @@ obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device_pci.o obj-$(CONFIG_PROC_THERMAL_MMIO_RAPL) += processor_thermal_rapl.o obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_rfim.o obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_mbox.o +obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_wt_req.o +obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_wt_hint.o +obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_power_floor.o obj-$(CONFIG_INT3406_THERMAL) += int3406_thermal.o obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c index ffc2871a02..427d370648 100644 --- a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c @@ -674,7 +674,7 @@ free_priv: return result; } -static int int3400_thermal_remove(struct platform_device *pdev) +static void int3400_thermal_remove(struct platform_device *pdev) { struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); @@ -698,7 +698,6 @@ static int int3400_thermal_remove(struct platform_device *pdev) kfree(priv->trts); kfree(priv->arts); kfree(priv); - return 0; } static const struct acpi_device_id int3400_thermal_match[] = { @@ -714,7 +713,7 @@ MODULE_DEVICE_TABLE(acpi, int3400_thermal_match); static struct platform_driver int3400_thermal_driver = { .probe = int3400_thermal_probe, - .remove = int3400_thermal_remove, + .remove_new = int3400_thermal_remove, .driver = { .name = "int3400 thermal", .acpi_match_table = ACPI_PTR(int3400_thermal_match), diff --git a/drivers/thermal/intel/int340x_thermal/int3401_thermal.c b/drivers/thermal/intel/int340x_thermal/int3401_thermal.c index c93a28eec4..193645a738 100644 --- a/drivers/thermal/intel/int340x_thermal/int3401_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3401_thermal.c @@ -36,11 +36,9 @@ static int int3401_add(struct platform_device *pdev) return ret; } -static int int3401_remove(struct platform_device *pdev) +static void int3401_remove(struct platform_device *pdev) { proc_thermal_remove(platform_get_drvdata(pdev)); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -62,7 +60,7 @@ static SIMPLE_DEV_PM_OPS(int3401_proc_thermal_pm, int3401_thermal_suspend, static struct platform_driver int3401_driver = { .probe = int3401_add, - .remove = int3401_remove, + .remove_new = int3401_remove, .driver = { .name = "int3401 thermal", .acpi_match_table = int3401_device_ids, diff --git a/drivers/thermal/intel/int340x_thermal/int3402_thermal.c b/drivers/thermal/intel/int340x_thermal/int3402_thermal.c index 43fa351e2b..ab8bfb5a39 100644 --- a/drivers/thermal/intel/int340x_thermal/int3402_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3402_thermal.c @@ -71,15 +71,13 @@ static int int3402_thermal_probe(struct platform_device *pdev) return 0; } -static int int3402_thermal_remove(struct platform_device *pdev) +static void int3402_thermal_remove(struct platform_device *pdev) { struct int3402_thermal_data *d = platform_get_drvdata(pdev); acpi_remove_notify_handler(d->handle, ACPI_DEVICE_NOTIFY, int3402_notify); int340x_thermal_zone_remove(d->int340x_zone); - - return 0; } static const struct acpi_device_id int3402_thermal_match[] = { @@ -91,7 +89,7 @@ MODULE_DEVICE_TABLE(acpi, int3402_thermal_match); static struct platform_driver int3402_thermal_driver = { .probe = int3402_thermal_probe, - .remove = int3402_thermal_remove, + .remove_new = int3402_thermal_remove, .driver = { .name = "int3402 thermal", .acpi_match_table = int3402_thermal_match, diff --git a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c index e418d270bc..9b33fd3a66 100644 --- a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c @@ -262,7 +262,7 @@ err: return result; } -static int int3403_remove(struct platform_device *pdev) +static void int3403_remove(struct platform_device *pdev) { struct int3403_priv *priv = platform_get_drvdata(pdev); @@ -277,8 +277,6 @@ static int int3403_remove(struct platform_device *pdev) default: break; } - - return 0; } static const struct acpi_device_id int3403_device_ids[] = { @@ -293,7 +291,7 @@ MODULE_DEVICE_TABLE(acpi, int3403_device_ids); static struct platform_driver int3403_driver = { .probe = int3403_add, - .remove = int3403_remove, + .remove_new = int3403_remove, .driver = { .name = "int3403 thermal", .acpi_match_table = int3403_device_ids, diff --git a/drivers/thermal/intel/int340x_thermal/int3406_thermal.c b/drivers/thermal/intel/int340x_thermal/int3406_thermal.c index f5e42fc2ac..1c266493c1 100644 --- a/drivers/thermal/intel/int340x_thermal/int3406_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3406_thermal.c @@ -178,13 +178,12 @@ err: return -ENODEV; } -static int int3406_thermal_remove(struct platform_device *pdev) +static void int3406_thermal_remove(struct platform_device *pdev) { struct int3406_thermal_data *d = platform_get_drvdata(pdev); thermal_cooling_device_unregister(d->cooling_dev); kfree(d->br); - return 0; } static const struct acpi_device_id int3406_thermal_match[] = { @@ -196,7 +195,7 @@ MODULE_DEVICE_TABLE(acpi, int3406_thermal_match); static struct platform_driver int3406_thermal_driver = { .probe = int3406_thermal_probe, - .remove = int3406_thermal_remove, + .remove_new = int3406_thermal_remove, .driver = { .name = "int3406 thermal", .acpi_match_table = int3406_thermal_match, diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c index 89cf007146..a03b67579d 100644 --- a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c +++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c @@ -67,6 +67,16 @@ static struct thermal_zone_device_ops int340x_thermal_zone_ops = { .critical = int340x_thermal_critical, }; +static inline void *int_to_trip_priv(int i) +{ + return (void *)(long)i; +} + +static inline int trip_priv_to_int(const struct thermal_trip *trip) +{ + return (long)trip->priv; +} + static int int340x_thermal_read_trips(struct acpi_device *zone_adev, struct thermal_trip *zone_trips, int trip_cnt) @@ -101,6 +111,7 @@ static int int340x_thermal_read_trips(struct acpi_device *zone_adev, break; zone_trips[trip_cnt].type = THERMAL_TRIP_ACTIVE; + zone_trips[trip_cnt].priv = int_to_trip_priv(i); trip_cnt++; } @@ -212,45 +223,40 @@ void int340x_thermal_zone_remove(struct int34x_thermal_zone *int34x_zone) } EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove); -void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone) +static int int340x_update_one_trip(struct thermal_trip *trip, void *arg) { - struct acpi_device *zone_adev = int34x_zone->adev; - struct thermal_trip *zone_trips = int34x_zone->trips; - int trip_cnt = int34x_zone->zone->num_trips; - int act_trip_nr = 0; - int i; - - mutex_lock(&int34x_zone->zone->lock); - - for (i = int34x_zone->aux_trip_nr; i < trip_cnt; i++) { - int temp, err; - - switch (zone_trips[i].type) { - case THERMAL_TRIP_CRITICAL: - err = thermal_acpi_critical_trip_temp(zone_adev, &temp); - break; - case THERMAL_TRIP_HOT: - err = thermal_acpi_hot_trip_temp(zone_adev, &temp); - break; - case THERMAL_TRIP_PASSIVE: - err = thermal_acpi_passive_trip_temp(zone_adev, &temp); - break; - case THERMAL_TRIP_ACTIVE: - err = thermal_acpi_active_trip_temp(zone_adev, act_trip_nr++, - &temp); - break; - default: - err = -ENODEV; - } - if (err) { - zone_trips[i].temperature = THERMAL_TEMP_INVALID; - continue; - } - - zone_trips[i].temperature = temp; + struct acpi_device *zone_adev = arg; + int temp, err; + + switch (trip->type) { + case THERMAL_TRIP_CRITICAL: + err = thermal_acpi_critical_trip_temp(zone_adev, &temp); + break; + case THERMAL_TRIP_HOT: + err = thermal_acpi_hot_trip_temp(zone_adev, &temp); + break; + case THERMAL_TRIP_PASSIVE: + err = thermal_acpi_passive_trip_temp(zone_adev, &temp); + break; + case THERMAL_TRIP_ACTIVE: + err = thermal_acpi_active_trip_temp(zone_adev, + trip_priv_to_int(trip), + &temp); + break; + default: + err = -ENODEV; } + if (err) + temp = THERMAL_TEMP_INVALID; - mutex_unlock(&int34x_zone->zone->lock); + trip->temperature = temp; + return 0; +} + +void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone) +{ + thermal_zone_for_each_trip(int34x_zone->zone, int340x_update_one_trip, + int34x_zone->adev); } EXPORT_SYMBOL_GPL(int340x_thermal_update_trips); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c index 3ca0a2f593..649f67fdf3 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c @@ -26,6 +26,48 @@ static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \ (unsigned long)proc_dev->power_limits[index].suffix * 1000); \ } +static ssize_t power_floor_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct proc_thermal_device *proc_dev = dev_get_drvdata(dev); + int ret; + + ret = proc_thermal_read_power_floor_status(proc_dev); + + return sysfs_emit(buf, "%d\n", ret); +} + +static ssize_t power_floor_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct proc_thermal_device *proc_dev = dev_get_drvdata(dev); + bool ret; + + ret = proc_thermal_power_floor_get_state(proc_dev); + + return sysfs_emit(buf, "%d\n", ret); +} + +static ssize_t power_floor_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct proc_thermal_device *proc_dev = dev_get_drvdata(dev); + u8 state; + int ret; + + if (kstrtou8(buf, 0, &state)) + return -EINVAL; + + ret = proc_thermal_power_floor_set_state(proc_dev, !!state); + if (ret) + return ret; + + return count; +} + POWER_LIMIT_SHOW(0, min_uw) POWER_LIMIT_SHOW(0, max_uw) POWER_LIMIT_SHOW(0, step_uw) @@ -50,6 +92,9 @@ static DEVICE_ATTR_RO(power_limit_1_step_uw); static DEVICE_ATTR_RO(power_limit_1_tmin_us); static DEVICE_ATTR_RO(power_limit_1_tmax_us); +static DEVICE_ATTR_RO(power_floor_status); +static DEVICE_ATTR_RW(power_floor_enable); + static struct attribute *power_limit_attrs[] = { &dev_attr_power_limit_0_min_uw.attr, &dev_attr_power_limit_1_min_uw.attr, @@ -61,12 +106,30 @@ static struct attribute *power_limit_attrs[] = { &dev_attr_power_limit_1_tmin_us.attr, &dev_attr_power_limit_0_tmax_us.attr, &dev_attr_power_limit_1_tmax_us.attr, + &dev_attr_power_floor_status.attr, + &dev_attr_power_floor_enable.attr, NULL }; +static umode_t power_limit_attr_visible(struct kobject *kobj, struct attribute *attr, int unused) +{ + struct device *dev = kobj_to_dev(kobj); + struct proc_thermal_device *proc_dev; + + if (attr != &dev_attr_power_floor_status.attr && attr != &dev_attr_power_floor_enable.attr) + return attr->mode; + + proc_dev = dev_get_drvdata(dev); + if (!proc_dev || !(proc_dev->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR)) + return 0; + + return attr->mode; +} + static const struct attribute_group power_limit_attribute_group = { .attrs = power_limit_attrs, - .name = "power_limits" + .name = "power_limits", + .is_visible = power_limit_attr_visible, }; static ssize_t tcc_offset_degree_celsius_show(struct device *dev, @@ -346,12 +409,18 @@ int proc_thermal_mmio_add(struct pci_dev *pdev, } } - if (feature_mask & PROC_THERMAL_FEATURE_MBOX) { - ret = proc_thermal_mbox_add(pdev, proc_priv); + if (feature_mask & PROC_THERMAL_FEATURE_WT_REQ) { + ret = proc_thermal_wt_req_add(pdev, proc_priv); if (ret) { dev_err(&pdev->dev, "failed to add MBOX interface\n"); goto err_rem_rfim; } + } else if (feature_mask & PROC_THERMAL_FEATURE_WT_HINT) { + ret = proc_thermal_wt_hint_add(pdev, proc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to add WT Hint\n"); + goto err_rem_rfim; + } } return 0; @@ -374,12 +443,18 @@ void proc_thermal_mmio_remove(struct pci_dev *pdev, struct proc_thermal_device * proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS) proc_thermal_rfim_remove(pdev); - if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_MBOX) - proc_thermal_mbox_remove(pdev); + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR) + proc_thermal_power_floor_set_state(proc_priv, false); + + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_REQ) + proc_thermal_wt_req_remove(pdev); + else if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_HINT) + proc_thermal_wt_hint_remove(pdev); } EXPORT_SYMBOL_GPL(proc_thermal_mmio_remove); MODULE_IMPORT_NS(INTEL_TCC); +MODULE_IMPORT_NS(INT340X_THERMAL); MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h index 7acaa8f1b8..95c6013a33 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h @@ -10,6 +10,7 @@ #include <linux/intel_rapl.h> #define PCI_DEVICE_ID_INTEL_ADL_THERMAL 0x461d +#define PCI_DEVICE_ID_INTEL_ARL_S_THERMAL 0xAD03 #define PCI_DEVICE_ID_INTEL_BDW_THERMAL 0x1603 #define PCI_DEVICE_ID_INTEL_BSW_THERMAL 0x22DC @@ -59,8 +60,10 @@ struct rapl_mmio_regs { #define PROC_THERMAL_FEATURE_RAPL 0x01 #define PROC_THERMAL_FEATURE_FIVR 0x02 #define PROC_THERMAL_FEATURE_DVFS 0x04 -#define PROC_THERMAL_FEATURE_MBOX 0x08 +#define PROC_THERMAL_FEATURE_WT_REQ 0x08 #define PROC_THERMAL_FEATURE_DLVR 0x10 +#define PROC_THERMAL_FEATURE_WT_HINT 0x20 +#define PROC_THERMAL_FEATURE_POWER_FLOOR 0x40 #if IS_ENABLED(CONFIG_PROC_THERMAL_MMIO_RAPL) int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv); @@ -80,13 +83,37 @@ static void __maybe_unused proc_thermal_rapl_remove(void) int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv); void proc_thermal_rfim_remove(struct pci_dev *pdev); -int proc_thermal_mbox_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv); -void proc_thermal_mbox_remove(struct pci_dev *pdev); +int proc_thermal_wt_req_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv); +void proc_thermal_wt_req_remove(struct pci_dev *pdev); + +#define MBOX_CMD_WORKLOAD_TYPE_READ 0x0E +#define MBOX_CMD_WORKLOAD_TYPE_WRITE 0x0F + +#define MBOX_DATA_BIT_AC_DC 30 +#define MBOX_DATA_BIT_VALID 31 + +#define SOC_WT_RES_INT_STATUS_OFFSET 0x5B18 +#define SOC_WT_RES_INT_STATUS_MASK GENMASK_ULL(3, 2) + +int proc_thermal_read_power_floor_status(struct proc_thermal_device *proc_priv); +int proc_thermal_power_floor_set_state(struct proc_thermal_device *proc_priv, bool enable); +bool proc_thermal_power_floor_get_state(struct proc_thermal_device *proc_priv); +void proc_thermal_power_floor_intr_callback(struct pci_dev *pdev, + struct proc_thermal_device *proc_priv); +bool proc_thermal_check_power_floor_intr(struct proc_thermal_device *proc_priv); int processor_thermal_send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp); int processor_thermal_send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data); +int processor_thermal_mbox_interrupt_config(struct pci_dev *pdev, bool enable, int enable_bit, + int time_window); int proc_thermal_add(struct device *dev, struct proc_thermal_device *priv); void proc_thermal_remove(struct proc_thermal_device *proc_priv); + +int proc_thermal_wt_hint_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv); +void proc_thermal_wt_hint_remove(struct pci_dev *pdev); +void proc_thermal_wt_intr_callback(struct pci_dev *pdev, struct proc_thermal_device *proc_priv); +bool proc_thermal_check_wt_intr(struct proc_thermal_device *proc_priv); + int proc_thermal_suspend(struct device *dev); int proc_thermal_resume(struct device *dev); int proc_thermal_mmio_add(struct pci_dev *pdev, diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c index 0d1e980072..d7495571dd 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c @@ -15,6 +15,11 @@ #define DRV_NAME "proc_thermal_pci" +static bool use_msi; +module_param(use_msi, bool, 0644); +MODULE_PARM_DESC(use_msi, + "Use PCI MSI based interrupts for processor thermal device."); + struct proc_thermal_pci { struct pci_dev *pdev; struct proc_thermal_device *proc_priv; @@ -117,20 +122,64 @@ static void pkg_thermal_schedule_work(struct delayed_work *work) schedule_delayed_work(work, ms); } +static void proc_thermal_clear_soc_int_status(struct proc_thermal_device *proc_priv) +{ + u64 status; + + if (!(proc_priv->mmio_feature_mask & + (PROC_THERMAL_FEATURE_WT_HINT | PROC_THERMAL_FEATURE_POWER_FLOOR))) + return; + + status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET); + writeq(status & ~SOC_WT_RES_INT_STATUS_MASK, + proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET); +} + +static irqreturn_t proc_thermal_irq_thread_handler(int irq, void *devid) +{ + struct proc_thermal_pci *pci_info = devid; + + proc_thermal_wt_intr_callback(pci_info->pdev, pci_info->proc_priv); + proc_thermal_power_floor_intr_callback(pci_info->pdev, pci_info->proc_priv); + proc_thermal_clear_soc_int_status(pci_info->proc_priv); + + return IRQ_HANDLED; +} + static irqreturn_t proc_thermal_irq_handler(int irq, void *devid) { struct proc_thermal_pci *pci_info = devid; + struct proc_thermal_device *proc_priv; + int ret = IRQ_HANDLED; u32 status; + proc_priv = pci_info->proc_priv; + + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_HINT) { + if (proc_thermal_check_wt_intr(pci_info->proc_priv)) + ret = IRQ_WAKE_THREAD; + } + + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR) { + if (proc_thermal_check_power_floor_intr(pci_info->proc_priv)) + ret = IRQ_WAKE_THREAD; + } + + /* + * Since now there are two sources of interrupts: one from thermal threshold + * and another from workload hint, add a check if there was really a threshold + * interrupt before scheduling work function for thermal threshold. + */ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_INT_STATUS_0, &status); + if (status) { + /* Disable enable interrupt flag */ + proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 0); + pkg_thermal_schedule_work(&pci_info->work); + } - /* Disable enable interrupt flag */ - proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 0); pci_write_config_byte(pci_info->pdev, 0xdc, 0x01); - pkg_thermal_schedule_work(&pci_info->work); - - return IRQ_HANDLED; + return ret; } static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp) @@ -203,6 +252,7 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_ struct proc_thermal_device *proc_priv; struct proc_thermal_pci *pci_info; int irq_flag = 0, irq, ret; + bool msi_irq = false; proc_priv = devm_kzalloc(&pdev->dev, sizeof(*proc_priv), GFP_KERNEL); if (!proc_priv) @@ -223,19 +273,19 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_ INIT_DELAYED_WORK(&pci_info->work, proc_thermal_threshold_work_fn); - ret = proc_thermal_add(&pdev->dev, proc_priv); - if (ret) { - dev_err(&pdev->dev, "error: proc_thermal_add, will continue\n"); - pci_info->no_legacy = 1; - } - proc_priv->priv_data = pci_info; pci_info->proc_priv = proc_priv; pci_set_drvdata(pdev, proc_priv); ret = proc_thermal_mmio_add(pdev, proc_priv, id->driver_data); if (ret) - goto err_ret_thermal; + return ret; + + ret = proc_thermal_add(&pdev->dev, proc_priv); + if (ret) { + dev_err(&pdev->dev, "error: proc_thermal_add, will continue\n"); + pci_info->no_legacy = 1; + } psv_trip.temperature = get_trip_temp(pci_info); @@ -245,21 +295,26 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_ &tzone_params, 0, 0); if (IS_ERR(pci_info->tzone)) { ret = PTR_ERR(pci_info->tzone); - goto err_ret_mmio; + goto err_del_legacy; } - /* request and enable interrupt */ - ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to allocate vectors!\n"); - goto err_ret_tzone; - } - if (!pdev->msi_enabled && !pdev->msix_enabled) + if (use_msi && (pdev->msi_enabled || pdev->msix_enabled)) { + /* request and enable interrupt */ + ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to allocate vectors!\n"); + goto err_ret_tzone; + } + + irq = pci_irq_vector(pdev, 0); + msi_irq = true; + } else { irq_flag = IRQF_SHARED; + irq = pdev->irq; + } - irq = pci_irq_vector(pdev, 0); ret = devm_request_threaded_irq(&pdev->dev, irq, - proc_thermal_irq_handler, NULL, + proc_thermal_irq_handler, proc_thermal_irq_thread_handler, irq_flag, KBUILD_MODNAME, pci_info); if (ret) { dev_err(&pdev->dev, "Request IRQ %d failed\n", pdev->irq); @@ -273,14 +328,14 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_ return 0; err_free_vectors: - pci_free_irq_vectors(pdev); + if (msi_irq) + pci_free_irq_vectors(pdev); err_ret_tzone: thermal_zone_device_unregister(pci_info->tzone); -err_ret_mmio: - proc_thermal_mmio_remove(pdev, proc_priv); -err_ret_thermal: +err_del_legacy: if (!pci_info->no_legacy) proc_thermal_remove(proc_priv); + proc_thermal_mmio_remove(pdev, proc_priv); pci_disable_device(pdev); return ret; @@ -350,9 +405,15 @@ static SIMPLE_DEV_PM_OPS(proc_thermal_pci_pm, proc_thermal_pci_suspend, proc_thermal_pci_resume); static const struct pci_device_id proc_thermal_pci_ids[] = { - { PCI_DEVICE_DATA(INTEL, ADL_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_MBOX) }, - { PCI_DEVICE_DATA(INTEL, MTLP_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_MBOX | PROC_THERMAL_FEATURE_DLVR) }, - { PCI_DEVICE_DATA(INTEL, RPL_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_MBOX) }, + { PCI_DEVICE_DATA(INTEL, ADL_THERMAL, PROC_THERMAL_FEATURE_RAPL | + PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_WT_REQ) }, + { PCI_DEVICE_DATA(INTEL, MTLP_THERMAL, PROC_THERMAL_FEATURE_RAPL | + PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_DLVR | + PROC_THERMAL_FEATURE_WT_HINT | PROC_THERMAL_FEATURE_POWER_FLOOR) }, + { PCI_DEVICE_DATA(INTEL, ARL_S_THERMAL, PROC_THERMAL_FEATURE_RAPL | + PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_DLVR | PROC_THERMAL_FEATURE_WT_HINT) }, + { PCI_DEVICE_DATA(INTEL, RPL_THERMAL, PROC_THERMAL_FEATURE_RAPL | + PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_WT_REQ) }, { }, }; @@ -368,6 +429,8 @@ static struct pci_driver proc_thermal_pci_driver = { module_pci_driver(proc_thermal_pci_driver); +MODULE_IMPORT_NS(INT340X_THERMAL); + MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c index 16fd9df5f3..ccfdd2f9d9 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c @@ -137,7 +137,8 @@ static const struct pci_device_id proc_thermal_pci_ids[] = { { PCI_DEVICE_DATA(INTEL, ICL_THERMAL, PROC_THERMAL_FEATURE_RAPL) }, { PCI_DEVICE_DATA(INTEL, JSL_THERMAL, 0) }, { PCI_DEVICE_DATA(INTEL, SKL_THERMAL, PROC_THERMAL_FEATURE_RAPL) }, - { PCI_DEVICE_DATA(INTEL, TGL_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_MBOX) }, + { PCI_DEVICE_DATA(INTEL, TGL_THERMAL, PROC_THERMAL_FEATURE_RAPL | + PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_WT_REQ) }, { }, }; diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c index 0b89a4340f..4d3bd32ff9 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c @@ -10,18 +10,12 @@ #include <linux/io-64-nonatomic-lo-hi.h> #include "processor_thermal_device.h" -#define MBOX_CMD_WORKLOAD_TYPE_READ 0x0E -#define MBOX_CMD_WORKLOAD_TYPE_WRITE 0x0F - #define MBOX_OFFSET_DATA 0x5810 #define MBOX_OFFSET_INTERFACE 0x5818 #define MBOX_BUSY_BIT 31 #define MBOX_RETRY_COUNT 100 -#define MBOX_DATA_BIT_VALID 31 -#define MBOX_DATA_BIT_AC_DC 30 - static DEFINE_MUTEX(mbox_lock); static int wait_for_mbox_ready(struct proc_thermal_device *proc_priv) @@ -51,23 +45,16 @@ static int send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data) int ret; proc_priv = pci_get_drvdata(pdev); - - mutex_lock(&mbox_lock); - ret = wait_for_mbox_ready(proc_priv); if (ret) - goto unlock_mbox; + return ret; writel(data, (proc_priv->mmio_base + MBOX_OFFSET_DATA)); /* Write command register */ reg_data = BIT_ULL(MBOX_BUSY_BIT) | id; writel(reg_data, (proc_priv->mmio_base + MBOX_OFFSET_INTERFACE)); - ret = wait_for_mbox_ready(proc_priv); - -unlock_mbox: - mutex_unlock(&mbox_lock); - return ret; + return wait_for_mbox_ready(proc_priv); } static int send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp) @@ -77,12 +64,9 @@ static int send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp) int ret; proc_priv = pci_get_drvdata(pdev); - - mutex_lock(&mbox_lock); - ret = wait_for_mbox_ready(proc_priv); if (ret) - goto unlock_mbox; + return ret; /* Write command register */ reg_data = BIT_ULL(MBOX_BUSY_BIT) | id; @@ -90,152 +74,85 @@ static int send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp) ret = wait_for_mbox_ready(proc_priv); if (ret) - goto unlock_mbox; + return ret; if (id == MBOX_CMD_WORKLOAD_TYPE_READ) *resp = readl(proc_priv->mmio_base + MBOX_OFFSET_DATA); else *resp = readq(proc_priv->mmio_base + MBOX_OFFSET_DATA); -unlock_mbox: - mutex_unlock(&mbox_lock); - return ret; + return 0; } int processor_thermal_send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp) { - return send_mbox_read_cmd(pdev, id, resp); + int ret; + + mutex_lock(&mbox_lock); + ret = send_mbox_read_cmd(pdev, id, resp); + mutex_unlock(&mbox_lock); + + return ret; } EXPORT_SYMBOL_NS_GPL(processor_thermal_send_mbox_read_cmd, INT340X_THERMAL); int processor_thermal_send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data) { - return send_mbox_write_cmd(pdev, id, data); -} -EXPORT_SYMBOL_NS_GPL(processor_thermal_send_mbox_write_cmd, INT340X_THERMAL); - -/* List of workload types */ -static const char * const workload_types[] = { - "none", - "idle", - "semi_active", - "bursty", - "sustained", - "battery_life", - NULL -}; - -static ssize_t workload_available_types_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int i = 0; - int ret = 0; - - while (workload_types[i] != NULL) - ret += sprintf(&buf[ret], "%s ", workload_types[i++]); + int ret; - ret += sprintf(&buf[ret], "\n"); + mutex_lock(&mbox_lock); + ret = send_mbox_write_cmd(pdev, id, data); + mutex_unlock(&mbox_lock); return ret; } +EXPORT_SYMBOL_NS_GPL(processor_thermal_send_mbox_write_cmd, INT340X_THERMAL); -static DEVICE_ATTR_RO(workload_available_types); - -static ssize_t workload_type_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct pci_dev *pdev = to_pci_dev(dev); - char str_preference[15]; - u32 data = 0; - ssize_t ret; - - ret = sscanf(buf, "%14s", str_preference); - if (ret != 1) - return -EINVAL; - - ret = match_string(workload_types, -1, str_preference); - if (ret < 0) - return ret; - - ret &= 0xff; - - if (ret) - data = BIT(MBOX_DATA_BIT_VALID) | BIT(MBOX_DATA_BIT_AC_DC); - - data |= ret; - - ret = send_mbox_write_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_WRITE, data); - if (ret) - return false; - - return count; -} +#define MBOX_CAMARILLO_RD_INTR_CONFIG 0x1E +#define MBOX_CAMARILLO_WR_INTR_CONFIG 0x1F +#define WLT_TW_MASK GENMASK_ULL(30, 24) +#define SOC_PREDICTION_TW_SHIFT 24 -static ssize_t workload_type_show(struct device *dev, - struct device_attribute *attr, - char *buf) +int processor_thermal_mbox_interrupt_config(struct pci_dev *pdev, bool enable, + int enable_bit, int time_window) { - struct pci_dev *pdev = to_pci_dev(dev); - u64 cmd_resp; + u64 data; int ret; - ret = send_mbox_read_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_READ, &cmd_resp); - if (ret) - return false; - - cmd_resp &= 0xff; - - if (cmd_resp > ARRAY_SIZE(workload_types) - 1) - return -EINVAL; + if (!pdev) + return -ENODEV; - return sprintf(buf, "%s\n", workload_types[cmd_resp]); -} - -static DEVICE_ATTR_RW(workload_type); + mutex_lock(&mbox_lock); -static struct attribute *workload_req_attrs[] = { - &dev_attr_workload_available_types.attr, - &dev_attr_workload_type.attr, - NULL -}; + /* Do read modify write for MBOX_CAMARILLO_RD_INTR_CONFIG */ -static const struct attribute_group workload_req_attribute_group = { - .attrs = workload_req_attrs, - .name = "workload_request" -}; + ret = send_mbox_read_cmd(pdev, MBOX_CAMARILLO_RD_INTR_CONFIG, &data); + if (ret) { + dev_err(&pdev->dev, "MBOX_CAMARILLO_RD_INTR_CONFIG failed\n"); + goto unlock; + } -static bool workload_req_created; + if (time_window >= 0) { + data &= ~WLT_TW_MASK; -int proc_thermal_mbox_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) -{ - u64 cmd_resp; - int ret; + /* Program notification delay */ + data |= ((u64)time_window << SOC_PREDICTION_TW_SHIFT) & WLT_TW_MASK; + } - /* Check if there is a mailbox support, if fails return success */ - ret = send_mbox_read_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_READ, &cmd_resp); - if (ret) - return 0; + if (enable) + data |= BIT(enable_bit); + else + data &= ~BIT(enable_bit); - ret = sysfs_create_group(&pdev->dev.kobj, &workload_req_attribute_group); + ret = send_mbox_write_cmd(pdev, MBOX_CAMARILLO_WR_INTR_CONFIG, data); if (ret) - return ret; + dev_err(&pdev->dev, "MBOX_CAMARILLO_WR_INTR_CONFIG failed\n"); - workload_req_created = true; - - return 0; -} -EXPORT_SYMBOL_GPL(proc_thermal_mbox_add); - -void proc_thermal_mbox_remove(struct pci_dev *pdev) -{ - if (workload_req_created) - sysfs_remove_group(&pdev->dev.kobj, &workload_req_attribute_group); - - workload_req_created = false; +unlock: + mutex_unlock(&mbox_lock); + return ret; } -EXPORT_SYMBOL_GPL(proc_thermal_mbox_remove); +EXPORT_SYMBOL_NS_GPL(processor_thermal_mbox_interrupt_config, INT340X_THERMAL); MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_power_floor.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_power_floor.c new file mode 100644 index 0000000000..a1a108407f --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_power_floor.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Processor thermal device module for registering and processing + * power floor. When the hardware reduces the power to the minimum + * possible, the power floor is notified via an interrupt. + * + * Operation: + * When user space enables power floor reporting: + * - Use mailbox to: + * Enable processor thermal device interrupt + * + * - Current status of power floor is read from offset 0x5B18 + * bit 39. + * + * Two interface functions are provided to call when there is a + * thermal device interrupt: + * - proc_thermal_power_floor_intr(): + * Check if the interrupt is for change in power floor. + * Called from interrupt context. + * + * - proc_thermal_power_floor_intr_callback(): + * Callback for interrupt processing in thread context. This involves + * sending notification to user space that there is a change in the + * power floor status. + * + * Copyright (c) 2023, Intel Corporation. + */ + +#include <linux/pci.h> +#include "processor_thermal_device.h" + +#define SOC_POWER_FLOOR_STATUS BIT(39) +#define SOC_POWER_FLOOR_SHIFT 39 + +#define SOC_POWER_FLOOR_INT_ENABLE_BIT 31 +#define SOC_POWER_FLOOR_INT_ACTIVE BIT(3) + +int proc_thermal_read_power_floor_status(struct proc_thermal_device *proc_priv) +{ + u64 status = 0; + + status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET); + return (status & SOC_POWER_FLOOR_STATUS) >> SOC_POWER_FLOOR_SHIFT; +} +EXPORT_SYMBOL_NS_GPL(proc_thermal_read_power_floor_status, INT340X_THERMAL); + +static bool enable_state; +static DEFINE_MUTEX(pf_lock); + +int proc_thermal_power_floor_set_state(struct proc_thermal_device *proc_priv, bool enable) +{ + int ret = 0; + + mutex_lock(&pf_lock); + if (enable_state == enable) + goto pf_unlock; + + /* + * Time window parameter is not applicable to power floor interrupt configuration. + * Hence use -1 for time window. + */ + ret = processor_thermal_mbox_interrupt_config(to_pci_dev(proc_priv->dev), enable, + SOC_POWER_FLOOR_INT_ENABLE_BIT, -1); + if (!ret) + enable_state = enable; + +pf_unlock: + mutex_unlock(&pf_lock); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(proc_thermal_power_floor_set_state, INT340X_THERMAL); + +bool proc_thermal_power_floor_get_state(struct proc_thermal_device *proc_priv) +{ + return enable_state; +} +EXPORT_SYMBOL_NS_GPL(proc_thermal_power_floor_get_state, INT340X_THERMAL); + +/** + * proc_thermal_check_power_floor_intr() - Check power floor interrupt. + * @proc_priv: Processor thermal device instance. + * + * Callback to check if the interrupt for power floor is active. + * + * Context: Called from interrupt context. + * + * Return: true if power floor is active, false when not active. + */ +bool proc_thermal_check_power_floor_intr(struct proc_thermal_device *proc_priv) +{ + u64 int_status; + + int_status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET); + return !!(int_status & SOC_POWER_FLOOR_INT_ACTIVE); +} +EXPORT_SYMBOL_NS_GPL(proc_thermal_check_power_floor_intr, INT340X_THERMAL); + +/** + * proc_thermal_power_floor_intr_callback() - Process power floor notification + * @pdev: PCI device instance + * @proc_priv: Processor thermal device instance. + * + * Check if the power floor interrupt is active, if active send notification to + * user space for the attribute "power_limits", so that user can read the attribute + * and take action. + * + * Context: Called from interrupt thread context. + * + * Return: None. + */ +void proc_thermal_power_floor_intr_callback(struct pci_dev *pdev, + struct proc_thermal_device *proc_priv) +{ + u64 status; + + status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET); + if (!(status & SOC_POWER_FLOOR_INT_ACTIVE)) + return; + + sysfs_notify(&pdev->dev.kobj, "power_limits", "power_floor_status"); +} +EXPORT_SYMBOL_NS_GPL(proc_thermal_power_floor_intr_callback, INT340X_THERMAL); + +MODULE_IMPORT_NS(INT340X_THERMAL); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c new file mode 100644 index 0000000000..9d5e4c169d --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * processor thermal device interface for reading workload type hints + * from the user space. The hints are provided by the firmware. + * + * Operation: + * When user space enables workload type prediction: + * - Use mailbox to: + * Configure notification delay + * Enable processor thermal device interrupt + * + * - The predicted workload type can be read from MMIO: + * Offset 0x5B18 shows if there was an interrupt + * active for change in workload type and also + * predicted workload type. + * + * Two interface functions are provided to call when there is a + * thermal device interrupt: + * - proc_thermal_check_wt_intr(): + * Check if the interrupt is for change in workload type. Called from + * interrupt context. + * + * - proc_thermal_wt_intr_callback(): + * Callback for interrupt processing in thread context. This involves + * sending notification to user space that there is a change in the + * workload type. + * + * Copyright (c) 2023, Intel Corporation. + */ + +#include <linux/bitfield.h> +#include <linux/pci.h> +#include "processor_thermal_device.h" + +#define SOC_WT GENMASK_ULL(47, 40) + +#define SOC_WT_PREDICTION_INT_ENABLE_BIT 23 + +#define SOC_WT_PREDICTION_INT_ACTIVE BIT(2) + +/* + * Closest possible to 1 Second is 1024 ms with programmed time delay + * of 0x0A. + */ +static u8 notify_delay = 0x0A; +static u16 notify_delay_ms = 1024; + +static DEFINE_MUTEX(wt_lock); +static u8 wt_enable; + +/* Show current predicted workload type index */ +static ssize_t workload_type_index_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct proc_thermal_device *proc_priv; + struct pci_dev *pdev = to_pci_dev(dev); + u64 status = 0; + int wt; + + mutex_lock(&wt_lock); + if (!wt_enable) { + mutex_unlock(&wt_lock); + return -ENODATA; + } + + proc_priv = pci_get_drvdata(pdev); + + status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET); + + mutex_unlock(&wt_lock); + + wt = FIELD_GET(SOC_WT, status); + + return sysfs_emit(buf, "%d\n", wt); +} + +static DEVICE_ATTR_RO(workload_type_index); + +static ssize_t workload_hint_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%d\n", wt_enable); +} + +static ssize_t workload_hint_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pci_dev *pdev = to_pci_dev(dev); + u8 mode; + int ret; + + if (kstrtou8(buf, 10, &mode) || mode > 1) + return -EINVAL; + + mutex_lock(&wt_lock); + + if (mode) + ret = processor_thermal_mbox_interrupt_config(pdev, true, + SOC_WT_PREDICTION_INT_ENABLE_BIT, + notify_delay); + else + ret = processor_thermal_mbox_interrupt_config(pdev, false, + SOC_WT_PREDICTION_INT_ENABLE_BIT, 0); + + if (ret) + goto ret_enable_store; + + ret = size; + wt_enable = mode; + +ret_enable_store: + mutex_unlock(&wt_lock); + + return ret; +} + +static DEVICE_ATTR_RW(workload_hint_enable); + +static ssize_t notification_delay_ms_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%u\n", notify_delay_ms); +} + +static ssize_t notification_delay_ms_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pci_dev *pdev = to_pci_dev(dev); + u16 new_tw; + int ret; + u8 tm; + + /* + * Time window register value: + * Formula: (1 + x/4) * power(2,y) + * x = 2 msbs, that is [30:29] y = 5 [28:24] + * in INTR_CONFIG register. + * The result will be in milli seconds. + * Here, just keep x = 0, and just change y. + * First round up the user value to power of 2 and + * then take log2, to get "y" value to program. + */ + ret = kstrtou16(buf, 10, &new_tw); + if (ret) + return ret; + + if (!new_tw) + return -EINVAL; + + new_tw = roundup_pow_of_two(new_tw); + tm = ilog2(new_tw); + if (tm > 31) + return -EINVAL; + + mutex_lock(&wt_lock); + + /* If the workload hint was already enabled, then update with the new delay */ + if (wt_enable) + ret = processor_thermal_mbox_interrupt_config(pdev, true, + SOC_WT_PREDICTION_INT_ENABLE_BIT, + tm); + + if (!ret) { + ret = size; + notify_delay = tm; + notify_delay_ms = new_tw; + } + + mutex_unlock(&wt_lock); + + return ret; +} + +static DEVICE_ATTR_RW(notification_delay_ms); + +static struct attribute *workload_hint_attrs[] = { + &dev_attr_workload_type_index.attr, + &dev_attr_workload_hint_enable.attr, + &dev_attr_notification_delay_ms.attr, + NULL +}; + +static const struct attribute_group workload_hint_attribute_group = { + .attrs = workload_hint_attrs, + .name = "workload_hint" +}; + +/* + * Callback to check if the interrupt for prediction is active. + * Caution: Called from the interrupt context. + */ +bool proc_thermal_check_wt_intr(struct proc_thermal_device *proc_priv) +{ + u64 int_status; + + int_status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET); + if (int_status & SOC_WT_PREDICTION_INT_ACTIVE) + return true; + + return false; +} +EXPORT_SYMBOL_NS_GPL(proc_thermal_check_wt_intr, INT340X_THERMAL); + +/* Callback to notify user space */ +void proc_thermal_wt_intr_callback(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) +{ + u64 status; + + status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET); + if (!(status & SOC_WT_PREDICTION_INT_ACTIVE)) + return; + + sysfs_notify(&pdev->dev.kobj, "workload_hint", "workload_type_index"); +} +EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_intr_callback, INT340X_THERMAL); + +static bool workload_hint_created; + +int proc_thermal_wt_hint_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) +{ + int ret; + + ret = sysfs_create_group(&pdev->dev.kobj, &workload_hint_attribute_group); + if (ret) + return ret; + + workload_hint_created = true; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_add, INT340X_THERMAL); + +void proc_thermal_wt_hint_remove(struct pci_dev *pdev) +{ + mutex_lock(&wt_lock); + if (wt_enable) + processor_thermal_mbox_interrupt_config(pdev, false, + SOC_WT_PREDICTION_INT_ENABLE_BIT, + 0); + mutex_unlock(&wt_lock); + + if (workload_hint_created) + sysfs_remove_group(&pdev->dev.kobj, &workload_hint_attribute_group); + + workload_hint_created = false; +} +EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_remove, INT340X_THERMAL); + +MODULE_IMPORT_NS(INT340X_THERMAL); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_req.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_req.c new file mode 100644 index 0000000000..711c4f761c --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_req.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * processor thermal device for Workload type hints + * update from user space + * + * Copyright (c) 2020-2023, Intel Corporation. + */ + +#include <linux/pci.h> +#include "processor_thermal_device.h" + +/* List of workload types */ +static const char * const workload_types[] = { + "none", + "idle", + "semi_active", + "bursty", + "sustained", + "battery_life", + NULL +}; + +static ssize_t workload_available_types_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i = 0; + int ret = 0; + + while (workload_types[i] != NULL) + ret += sprintf(&buf[ret], "%s ", workload_types[i++]); + + ret += sprintf(&buf[ret], "\n"); + + return ret; +} + +static DEVICE_ATTR_RO(workload_available_types); + +static ssize_t workload_type_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(dev); + char str_preference[15]; + u32 data = 0; + ssize_t ret; + + ret = sscanf(buf, "%14s", str_preference); + if (ret != 1) + return -EINVAL; + + ret = match_string(workload_types, -1, str_preference); + if (ret < 0) + return ret; + + ret &= 0xff; + + if (ret) + data = BIT(MBOX_DATA_BIT_VALID) | BIT(MBOX_DATA_BIT_AC_DC); + + data |= ret; + + ret = processor_thermal_send_mbox_write_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_WRITE, data); + if (ret) + return false; + + return count; +} + +static ssize_t workload_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + u64 cmd_resp; + int ret; + + ret = processor_thermal_send_mbox_read_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_READ, &cmd_resp); + if (ret) + return false; + + cmd_resp &= 0xff; + + if (cmd_resp > ARRAY_SIZE(workload_types) - 1) + return -EINVAL; + + return sprintf(buf, "%s\n", workload_types[cmd_resp]); +} + +static DEVICE_ATTR_RW(workload_type); + +static struct attribute *workload_req_attrs[] = { + &dev_attr_workload_available_types.attr, + &dev_attr_workload_type.attr, + NULL +}; + +static const struct attribute_group workload_req_attribute_group = { + .attrs = workload_req_attrs, + .name = "workload_request" +}; + +static bool workload_req_created; + +int proc_thermal_wt_req_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) +{ + u64 cmd_resp; + int ret; + + /* Check if there is a mailbox support, if fails return success */ + ret = processor_thermal_send_mbox_read_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_READ, &cmd_resp); + if (ret) + return 0; + + ret = sysfs_create_group(&pdev->dev.kobj, &workload_req_attribute_group); + if (ret) + return ret; + + workload_req_created = true; + + return 0; +} +EXPORT_SYMBOL_GPL(proc_thermal_wt_req_add); + +void proc_thermal_wt_req_remove(struct pci_dev *pdev) +{ + if (workload_req_created) + sysfs_remove_group(&pdev->dev.kobj, &workload_req_attribute_group); + + workload_req_created = false; +} +EXPORT_SYMBOL_GPL(proc_thermal_wt_req_remove); + +MODULE_IMPORT_NS(INT340X_THERMAL); +MODULE_LICENSE("GPL"); |