18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * via-cputemp.c - Driver for VIA CPU core temperature monitoring 48c2ecf20Sopenharmony_ci * Copyright (C) 2009 VIA Technologies, Inc. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * based on existing coretemp.c, which is 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Copyright (C) 2007 Rudolf Marek <r.marek@assembler.cz> 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/init.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 178c2ecf20Sopenharmony_ci#include <linux/hwmon-vid.h> 188c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 198c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h> 208c2ecf20Sopenharmony_ci#include <linux/err.h> 218c2ecf20Sopenharmony_ci#include <linux/mutex.h> 228c2ecf20Sopenharmony_ci#include <linux/list.h> 238c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 248c2ecf20Sopenharmony_ci#include <linux/cpu.h> 258c2ecf20Sopenharmony_ci#include <asm/msr.h> 268c2ecf20Sopenharmony_ci#include <asm/processor.h> 278c2ecf20Sopenharmony_ci#include <asm/cpu_device_id.h> 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define DRVNAME "via_cputemp" 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cienum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME }; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci/* 348c2ecf20Sopenharmony_ci * Functions declaration 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistruct via_cputemp_data { 388c2ecf20Sopenharmony_ci struct device *hwmon_dev; 398c2ecf20Sopenharmony_ci const char *name; 408c2ecf20Sopenharmony_ci u8 vrm; 418c2ecf20Sopenharmony_ci u32 id; 428c2ecf20Sopenharmony_ci u32 msr_temp; 438c2ecf20Sopenharmony_ci u32 msr_vid; 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci/* 478c2ecf20Sopenharmony_ci * Sysfs stuff 488c2ecf20Sopenharmony_ci */ 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic ssize_t name_show(struct device *dev, struct device_attribute *devattr, 518c2ecf20Sopenharmony_ci char *buf) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci int ret; 548c2ecf20Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 558c2ecf20Sopenharmony_ci struct via_cputemp_data *data = dev_get_drvdata(dev); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci if (attr->index == SHOW_NAME) 588c2ecf20Sopenharmony_ci ret = sprintf(buf, "%s\n", data->name); 598c2ecf20Sopenharmony_ci else /* show label */ 608c2ecf20Sopenharmony_ci ret = sprintf(buf, "Core %d\n", data->id); 618c2ecf20Sopenharmony_ci return ret; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic ssize_t temp_show(struct device *dev, struct device_attribute *devattr, 658c2ecf20Sopenharmony_ci char *buf) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci struct via_cputemp_data *data = dev_get_drvdata(dev); 688c2ecf20Sopenharmony_ci u32 eax, edx; 698c2ecf20Sopenharmony_ci int err; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx); 728c2ecf20Sopenharmony_ci if (err) 738c2ecf20Sopenharmony_ci return -EAGAIN; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci return sprintf(buf, "%lu\n", ((unsigned long)eax & 0xffffff) * 1000); 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic ssize_t cpu0_vid_show(struct device *dev, 798c2ecf20Sopenharmony_ci struct device_attribute *devattr, char *buf) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci struct via_cputemp_data *data = dev_get_drvdata(dev); 828c2ecf20Sopenharmony_ci u32 eax, edx; 838c2ecf20Sopenharmony_ci int err; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci err = rdmsr_safe_on_cpu(data->id, data->msr_vid, &eax, &edx); 868c2ecf20Sopenharmony_ci if (err) 878c2ecf20Sopenharmony_ci return -EAGAIN; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return sprintf(buf, "%d\n", vid_from_reg(~edx & 0x7f, data->vrm)); 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_input, temp, SHOW_TEMP); 938c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_label, name, SHOW_LABEL); 948c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(name, name, SHOW_NAME); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic struct attribute *via_cputemp_attributes[] = { 978c2ecf20Sopenharmony_ci &sensor_dev_attr_name.dev_attr.attr, 988c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_label.dev_attr.attr, 998c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_input.dev_attr.attr, 1008c2ecf20Sopenharmony_ci NULL 1018c2ecf20Sopenharmony_ci}; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic const struct attribute_group via_cputemp_group = { 1048c2ecf20Sopenharmony_ci .attrs = via_cputemp_attributes, 1058c2ecf20Sopenharmony_ci}; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci/* Optional attributes */ 1088c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(cpu0_vid); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic int via_cputemp_probe(struct platform_device *pdev) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci struct via_cputemp_data *data; 1138c2ecf20Sopenharmony_ci struct cpuinfo_x86 *c = &cpu_data(pdev->id); 1148c2ecf20Sopenharmony_ci int err; 1158c2ecf20Sopenharmony_ci u32 eax, edx; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci data = devm_kzalloc(&pdev->dev, sizeof(struct via_cputemp_data), 1188c2ecf20Sopenharmony_ci GFP_KERNEL); 1198c2ecf20Sopenharmony_ci if (!data) 1208c2ecf20Sopenharmony_ci return -ENOMEM; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci data->id = pdev->id; 1238c2ecf20Sopenharmony_ci data->name = "via_cputemp"; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci if (c->x86 == 7) { 1268c2ecf20Sopenharmony_ci data->msr_temp = 0x1423; 1278c2ecf20Sopenharmony_ci } else { 1288c2ecf20Sopenharmony_ci switch (c->x86_model) { 1298c2ecf20Sopenharmony_ci case 0xA: 1308c2ecf20Sopenharmony_ci /* C7 A */ 1318c2ecf20Sopenharmony_ci case 0xD: 1328c2ecf20Sopenharmony_ci /* C7 D */ 1338c2ecf20Sopenharmony_ci data->msr_temp = 0x1169; 1348c2ecf20Sopenharmony_ci data->msr_vid = 0x198; 1358c2ecf20Sopenharmony_ci break; 1368c2ecf20Sopenharmony_ci case 0xF: 1378c2ecf20Sopenharmony_ci /* Nano */ 1388c2ecf20Sopenharmony_ci data->msr_temp = 0x1423; 1398c2ecf20Sopenharmony_ci break; 1408c2ecf20Sopenharmony_ci default: 1418c2ecf20Sopenharmony_ci return -ENODEV; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci /* test if we can access the TEMPERATURE MSR */ 1468c2ecf20Sopenharmony_ci err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx); 1478c2ecf20Sopenharmony_ci if (err) { 1488c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 1498c2ecf20Sopenharmony_ci "Unable to access TEMPERATURE MSR, giving up\n"); 1508c2ecf20Sopenharmony_ci return err; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, data); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci err = sysfs_create_group(&pdev->dev.kobj, &via_cputemp_group); 1568c2ecf20Sopenharmony_ci if (err) 1578c2ecf20Sopenharmony_ci return err; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if (data->msr_vid) 1608c2ecf20Sopenharmony_ci data->vrm = vid_which_vrm(); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci if (data->vrm) { 1638c2ecf20Sopenharmony_ci err = device_create_file(&pdev->dev, &dev_attr_cpu0_vid); 1648c2ecf20Sopenharmony_ci if (err) 1658c2ecf20Sopenharmony_ci goto exit_remove; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci data->hwmon_dev = hwmon_device_register(&pdev->dev); 1698c2ecf20Sopenharmony_ci if (IS_ERR(data->hwmon_dev)) { 1708c2ecf20Sopenharmony_ci err = PTR_ERR(data->hwmon_dev); 1718c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Class registration failed (%d)\n", 1728c2ecf20Sopenharmony_ci err); 1738c2ecf20Sopenharmony_ci goto exit_remove; 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci return 0; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ciexit_remove: 1798c2ecf20Sopenharmony_ci if (data->vrm) 1808c2ecf20Sopenharmony_ci device_remove_file(&pdev->dev, &dev_attr_cpu0_vid); 1818c2ecf20Sopenharmony_ci sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group); 1828c2ecf20Sopenharmony_ci return err; 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cistatic int via_cputemp_remove(struct platform_device *pdev) 1868c2ecf20Sopenharmony_ci{ 1878c2ecf20Sopenharmony_ci struct via_cputemp_data *data = platform_get_drvdata(pdev); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci hwmon_device_unregister(data->hwmon_dev); 1908c2ecf20Sopenharmony_ci if (data->vrm) 1918c2ecf20Sopenharmony_ci device_remove_file(&pdev->dev, &dev_attr_cpu0_vid); 1928c2ecf20Sopenharmony_ci sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group); 1938c2ecf20Sopenharmony_ci return 0; 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic struct platform_driver via_cputemp_driver = { 1978c2ecf20Sopenharmony_ci .driver = { 1988c2ecf20Sopenharmony_ci .name = DRVNAME, 1998c2ecf20Sopenharmony_ci }, 2008c2ecf20Sopenharmony_ci .probe = via_cputemp_probe, 2018c2ecf20Sopenharmony_ci .remove = via_cputemp_remove, 2028c2ecf20Sopenharmony_ci}; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_cistruct pdev_entry { 2058c2ecf20Sopenharmony_ci struct list_head list; 2068c2ecf20Sopenharmony_ci struct platform_device *pdev; 2078c2ecf20Sopenharmony_ci unsigned int cpu; 2088c2ecf20Sopenharmony_ci}; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cistatic LIST_HEAD(pdev_list); 2118c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(pdev_list_mutex); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic int via_cputemp_online(unsigned int cpu) 2148c2ecf20Sopenharmony_ci{ 2158c2ecf20Sopenharmony_ci int err; 2168c2ecf20Sopenharmony_ci struct platform_device *pdev; 2178c2ecf20Sopenharmony_ci struct pdev_entry *pdev_entry; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci pdev = platform_device_alloc(DRVNAME, cpu); 2208c2ecf20Sopenharmony_ci if (!pdev) { 2218c2ecf20Sopenharmony_ci err = -ENOMEM; 2228c2ecf20Sopenharmony_ci pr_err("Device allocation failed\n"); 2238c2ecf20Sopenharmony_ci goto exit; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL); 2278c2ecf20Sopenharmony_ci if (!pdev_entry) { 2288c2ecf20Sopenharmony_ci err = -ENOMEM; 2298c2ecf20Sopenharmony_ci goto exit_device_put; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci err = platform_device_add(pdev); 2338c2ecf20Sopenharmony_ci if (err) { 2348c2ecf20Sopenharmony_ci pr_err("Device addition failed (%d)\n", err); 2358c2ecf20Sopenharmony_ci goto exit_device_free; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci pdev_entry->pdev = pdev; 2398c2ecf20Sopenharmony_ci pdev_entry->cpu = cpu; 2408c2ecf20Sopenharmony_ci mutex_lock(&pdev_list_mutex); 2418c2ecf20Sopenharmony_ci list_add_tail(&pdev_entry->list, &pdev_list); 2428c2ecf20Sopenharmony_ci mutex_unlock(&pdev_list_mutex); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci return 0; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ciexit_device_free: 2478c2ecf20Sopenharmony_ci kfree(pdev_entry); 2488c2ecf20Sopenharmony_ciexit_device_put: 2498c2ecf20Sopenharmony_ci platform_device_put(pdev); 2508c2ecf20Sopenharmony_ciexit: 2518c2ecf20Sopenharmony_ci return err; 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int via_cputemp_down_prep(unsigned int cpu) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci struct pdev_entry *p; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci mutex_lock(&pdev_list_mutex); 2598c2ecf20Sopenharmony_ci list_for_each_entry(p, &pdev_list, list) { 2608c2ecf20Sopenharmony_ci if (p->cpu == cpu) { 2618c2ecf20Sopenharmony_ci platform_device_unregister(p->pdev); 2628c2ecf20Sopenharmony_ci list_del(&p->list); 2638c2ecf20Sopenharmony_ci mutex_unlock(&pdev_list_mutex); 2648c2ecf20Sopenharmony_ci kfree(p); 2658c2ecf20Sopenharmony_ci return 0; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci mutex_unlock(&pdev_list_mutex); 2698c2ecf20Sopenharmony_ci return 0; 2708c2ecf20Sopenharmony_ci} 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic const struct x86_cpu_id __initconst cputemp_ids[] = { 2738c2ecf20Sopenharmony_ci X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 6, X86_CENTAUR_FAM6_C7_A, NULL), 2748c2ecf20Sopenharmony_ci X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 6, X86_CENTAUR_FAM6_C7_D, NULL), 2758c2ecf20Sopenharmony_ci X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 6, X86_CENTAUR_FAM6_NANO, NULL), 2768c2ecf20Sopenharmony_ci X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 7, X86_MODEL_ANY, NULL), 2778c2ecf20Sopenharmony_ci {} 2788c2ecf20Sopenharmony_ci}; 2798c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(x86cpu, cputemp_ids); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cistatic enum cpuhp_state via_temp_online; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic int __init via_cputemp_init(void) 2848c2ecf20Sopenharmony_ci{ 2858c2ecf20Sopenharmony_ci int err; 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci if (!x86_match_cpu(cputemp_ids)) 2888c2ecf20Sopenharmony_ci return -ENODEV; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci err = platform_driver_register(&via_cputemp_driver); 2918c2ecf20Sopenharmony_ci if (err) 2928c2ecf20Sopenharmony_ci goto exit; 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hwmon/via:online", 2958c2ecf20Sopenharmony_ci via_cputemp_online, via_cputemp_down_prep); 2968c2ecf20Sopenharmony_ci if (err < 0) 2978c2ecf20Sopenharmony_ci goto exit_driver_unreg; 2988c2ecf20Sopenharmony_ci via_temp_online = err; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci#ifndef CONFIG_HOTPLUG_CPU 3018c2ecf20Sopenharmony_ci if (list_empty(&pdev_list)) { 3028c2ecf20Sopenharmony_ci err = -ENODEV; 3038c2ecf20Sopenharmony_ci goto exit_hp_unreg; 3048c2ecf20Sopenharmony_ci } 3058c2ecf20Sopenharmony_ci#endif 3068c2ecf20Sopenharmony_ci return 0; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci#ifndef CONFIG_HOTPLUG_CPU 3098c2ecf20Sopenharmony_ciexit_hp_unreg: 3108c2ecf20Sopenharmony_ci cpuhp_remove_state_nocalls(via_temp_online); 3118c2ecf20Sopenharmony_ci#endif 3128c2ecf20Sopenharmony_ciexit_driver_unreg: 3138c2ecf20Sopenharmony_ci platform_driver_unregister(&via_cputemp_driver); 3148c2ecf20Sopenharmony_ciexit: 3158c2ecf20Sopenharmony_ci return err; 3168c2ecf20Sopenharmony_ci} 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_cistatic void __exit via_cputemp_exit(void) 3198c2ecf20Sopenharmony_ci{ 3208c2ecf20Sopenharmony_ci cpuhp_remove_state(via_temp_online); 3218c2ecf20Sopenharmony_ci platform_driver_unregister(&via_cputemp_driver); 3228c2ecf20Sopenharmony_ci} 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Harald Welte <HaraldWelte@viatech.com>"); 3258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("VIA CPU temperature monitor"); 3268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_cimodule_init(via_cputemp_init) 3298c2ecf20Sopenharmony_cimodule_exit(via_cputemp_exit) 330