| /* | |
| * thermal support for the cell processor | |
| * | |
| * This module adds some sysfs attributes to cpu and spu nodes. | |
| * Base for measurements are the digital thermal sensors (DTS) | |
| * located on the chip. | |
| * The accuracy is 2 degrees, starting from 65 up to 125 degrees celsius | |
| * The attributes can be found under | |
| * /sys/devices/system/cpu/cpuX/thermal | |
| * /sys/devices/system/spu/spuX/thermal | |
| * | |
| * The following attributes are added for each node: | |
| * temperature: | |
| * contains the current temperature measured by the DTS | |
| * throttle_begin: | |
| * throttling begins when temperature is greater or equal to | |
| * throttle_begin. Setting this value to 125 prevents throttling. | |
| * throttle_end: | |
| * throttling is being ceased, if the temperature is lower than | |
| * throttle_end. Due to a delay between applying throttling and | |
| * a reduced temperature this value should be less than throttle_begin. | |
| * A value equal to throttle_begin provides only a very little hysteresis. | |
| * throttle_full_stop: | |
| * If the temperatrue is greater or equal to throttle_full_stop, | |
| * full throttling is applied to the cpu or spu. This value should be | |
| * greater than throttle_begin and throttle_end. Setting this value to | |
| * 65 prevents the unit from running code at all. | |
| * | |
| * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 | |
| * | |
| * Author: Christian Krafft <krafft@de.ibm.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/device.h> | |
| #include <linux/kernel.h> | |
| #include <linux/cpu.h> | |
| #include <asm/spu.h> | |
| #include <asm/io.h> | |
| #include <asm/prom.h> | |
| #include <asm/cell-regs.h> | |
| #include "spu_priv1_mmio.h" | |
| #define TEMP_MIN 65 | |
| #define TEMP_MAX 125 | |
| #define DEVICE_PREFIX_ATTR(_prefix,_name,_mode) \ | |
| struct device_attribute attr_ ## _prefix ## _ ## _name = { \ | |
| .attr = { .name = __stringify(_name), .mode = _mode }, \ | |
| .show = _prefix ## _show_ ## _name, \ | |
| .store = _prefix ## _store_ ## _name, \ | |
| }; | |
| static inline u8 reg_to_temp(u8 reg_value) | |
| { | |
| return ((reg_value & 0x3f) << 1) + TEMP_MIN; | |
| } | |
| static inline u8 temp_to_reg(u8 temp) | |
| { | |
| return ((temp - TEMP_MIN) >> 1) & 0x3f; | |
| } | |
| static struct cbe_pmd_regs __iomem *get_pmd_regs(struct device *dev) | |
| { | |
| struct spu *spu; | |
| spu = container_of(dev, struct spu, dev); | |
| return cbe_get_pmd_regs(spu_devnode(spu)); | |
| } | |
| /* returns the value for a given spu in a given register */ | |
| static u8 spu_read_register_value(struct device *dev, union spe_reg __iomem *reg) | |
| { | |
| union spe_reg value; | |
| struct spu *spu; | |
| spu = container_of(dev, struct spu, dev); | |
| value.val = in_be64(®->val); | |
| return value.spe[spu->spe_id]; | |
| } | |
| static ssize_t spu_show_temp(struct device *dev, struct device_attribute *attr, | |
| char *buf) | |
| { | |
| u8 value; | |
| struct cbe_pmd_regs __iomem *pmd_regs; | |
| pmd_regs = get_pmd_regs(dev); | |
| value = spu_read_register_value(dev, &pmd_regs->ts_ctsr1); | |
| return sprintf(buf, "%d\n", reg_to_temp(value)); | |
| } | |
| static ssize_t show_throttle(struct cbe_pmd_regs __iomem *pmd_regs, char *buf, int pos) | |
| { | |
| u64 value; | |
| value = in_be64(&pmd_regs->tm_tpr.val); | |
| /* access the corresponding byte */ | |
| value >>= pos; | |
| value &= 0x3F; | |
| return sprintf(buf, "%d\n", reg_to_temp(value)); | |
| } | |
| static ssize_t store_throttle(struct cbe_pmd_regs __iomem *pmd_regs, const char *buf, size_t size, int pos) | |
| { | |
| u64 reg_value; | |
| unsigned int temp; | |
| u64 new_value; | |
| int ret; | |
| ret = sscanf(buf, "%u", &temp); | |
| if (ret != 1 || temp < TEMP_MIN || temp > TEMP_MAX) | |
| return -EINVAL; | |
| new_value = temp_to_reg(temp); | |
| reg_value = in_be64(&pmd_regs->tm_tpr.val); | |
| /* zero out bits for new value */ | |
| reg_value &= ~(0xffull << pos); | |
| /* set bits to new value */ | |
| reg_value |= new_value << pos; | |
| out_be64(&pmd_regs->tm_tpr.val, reg_value); | |
| return size; | |
| } | |
| static ssize_t spu_show_throttle_end(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return show_throttle(get_pmd_regs(dev), buf, 0); | |
| } | |
| static ssize_t spu_show_throttle_begin(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return show_throttle(get_pmd_regs(dev), buf, 8); | |
| } | |
| static ssize_t spu_show_throttle_full_stop(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return show_throttle(get_pmd_regs(dev), buf, 16); | |
| } | |
| static ssize_t spu_store_throttle_end(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t size) | |
| { | |
| return store_throttle(get_pmd_regs(dev), buf, size, 0); | |
| } | |
| static ssize_t spu_store_throttle_begin(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t size) | |
| { | |
| return store_throttle(get_pmd_regs(dev), buf, size, 8); | |
| } | |
| static ssize_t spu_store_throttle_full_stop(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t size) | |
| { | |
| return store_throttle(get_pmd_regs(dev), buf, size, 16); | |
| } | |
| static ssize_t ppe_show_temp(struct device *dev, char *buf, int pos) | |
| { | |
| struct cbe_pmd_regs __iomem *pmd_regs; | |
| u64 value; | |
| pmd_regs = cbe_get_cpu_pmd_regs(dev->id); | |
| value = in_be64(&pmd_regs->ts_ctsr2); | |
| value = (value >> pos) & 0x3f; | |
| return sprintf(buf, "%d\n", reg_to_temp(value)); | |
| } | |
| /* shows the temperature of the DTS on the PPE, | |
| * located near the linear thermal sensor */ | |
| static ssize_t ppe_show_temp0(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return ppe_show_temp(dev, buf, 32); | |
| } | |
| /* shows the temperature of the second DTS on the PPE */ | |
| static ssize_t ppe_show_temp1(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return ppe_show_temp(dev, buf, 0); | |
| } | |
| static ssize_t ppe_show_throttle_end(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return show_throttle(cbe_get_cpu_pmd_regs(dev->id), buf, 32); | |
| } | |
| static ssize_t ppe_show_throttle_begin(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return show_throttle(cbe_get_cpu_pmd_regs(dev->id), buf, 40); | |
| } | |
| static ssize_t ppe_show_throttle_full_stop(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return show_throttle(cbe_get_cpu_pmd_regs(dev->id), buf, 48); | |
| } | |
| static ssize_t ppe_store_throttle_end(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t size) | |
| { | |
| return store_throttle(cbe_get_cpu_pmd_regs(dev->id), buf, size, 32); | |
| } | |
| static ssize_t ppe_store_throttle_begin(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t size) | |
| { | |
| return store_throttle(cbe_get_cpu_pmd_regs(dev->id), buf, size, 40); | |
| } | |
| static ssize_t ppe_store_throttle_full_stop(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t size) | |
| { | |
| return store_throttle(cbe_get_cpu_pmd_regs(dev->id), buf, size, 48); | |
| } | |
| static struct device_attribute attr_spu_temperature = { | |
| .attr = {.name = "temperature", .mode = 0400 }, | |
| .show = spu_show_temp, | |
| }; | |
| static DEVICE_PREFIX_ATTR(spu, throttle_end, 0600); | |
| static DEVICE_PREFIX_ATTR(spu, throttle_begin, 0600); | |
| static DEVICE_PREFIX_ATTR(spu, throttle_full_stop, 0600); | |
| static struct attribute *spu_attributes[] = { | |
| &attr_spu_temperature.attr, | |
| &attr_spu_throttle_end.attr, | |
| &attr_spu_throttle_begin.attr, | |
| &attr_spu_throttle_full_stop.attr, | |
| NULL, | |
| }; | |
| static struct attribute_group spu_attribute_group = { | |
| .name = "thermal", | |
| .attrs = spu_attributes, | |
| }; | |
| static struct device_attribute attr_ppe_temperature0 = { | |
| .attr = {.name = "temperature0", .mode = 0400 }, | |
| .show = ppe_show_temp0, | |
| }; | |
| static struct device_attribute attr_ppe_temperature1 = { | |
| .attr = {.name = "temperature1", .mode = 0400 }, | |
| .show = ppe_show_temp1, | |
| }; | |
| static DEVICE_PREFIX_ATTR(ppe, throttle_end, 0600); | |
| static DEVICE_PREFIX_ATTR(ppe, throttle_begin, 0600); | |
| static DEVICE_PREFIX_ATTR(ppe, throttle_full_stop, 0600); | |
| static struct attribute *ppe_attributes[] = { | |
| &attr_ppe_temperature0.attr, | |
| &attr_ppe_temperature1.attr, | |
| &attr_ppe_throttle_end.attr, | |
| &attr_ppe_throttle_begin.attr, | |
| &attr_ppe_throttle_full_stop.attr, | |
| NULL, | |
| }; | |
| static struct attribute_group ppe_attribute_group = { | |
| .name = "thermal", | |
| .attrs = ppe_attributes, | |
| }; | |
| /* | |
| * initialize throttling with default values | |
| */ | |
| static int __init init_default_values(void) | |
| { | |
| int cpu; | |
| struct cbe_pmd_regs __iomem *pmd_regs; | |
| struct device *dev; | |
| union ppe_spe_reg tpr; | |
| union spe_reg str1; | |
| u64 str2; | |
| union spe_reg cr1; | |
| u64 cr2; | |
| /* TPR defaults */ | |
| /* ppe | |
| * 1F - no full stop | |
| * 08 - dynamic throttling starts if over 80 degrees | |
| * 03 - dynamic throttling ceases if below 70 degrees */ | |
| tpr.ppe = 0x1F0803; | |
| /* spe | |
| * 10 - full stopped when over 96 degrees | |
| * 08 - dynamic throttling starts if over 80 degrees | |
| * 03 - dynamic throttling ceases if below 70 degrees | |
| */ | |
| tpr.spe = 0x100803; | |
| /* STR defaults */ | |
| /* str1 | |
| * 10 - stop 16 of 32 cycles | |
| */ | |
| str1.val = 0x1010101010101010ull; | |
| /* str2 | |
| * 10 - stop 16 of 32 cycles | |
| */ | |
| str2 = 0x10; | |
| /* CR defaults */ | |
| /* cr1 | |
| * 4 - normal operation | |
| */ | |
| cr1.val = 0x0404040404040404ull; | |
| /* cr2 | |
| * 4 - normal operation | |
| */ | |
| cr2 = 0x04; | |
| for_each_possible_cpu (cpu) { | |
| pr_debug("processing cpu %d\n", cpu); | |
| dev = get_cpu_device(cpu); | |
| if (!dev) { | |
| pr_info("invalid dev pointer for cbe_thermal\n"); | |
| return -EINVAL; | |
| } | |
| pmd_regs = cbe_get_cpu_pmd_regs(dev->id); | |
| if (!pmd_regs) { | |
| pr_info("invalid CBE regs pointer for cbe_thermal\n"); | |
| return -EINVAL; | |
| } | |
| out_be64(&pmd_regs->tm_str2, str2); | |
| out_be64(&pmd_regs->tm_str1.val, str1.val); | |
| out_be64(&pmd_regs->tm_tpr.val, tpr.val); | |
| out_be64(&pmd_regs->tm_cr1.val, cr1.val); | |
| out_be64(&pmd_regs->tm_cr2, cr2); | |
| } | |
| return 0; | |
| } | |
| static int __init thermal_init(void) | |
| { | |
| int rc = init_default_values(); | |
| if (rc == 0) { | |
| spu_add_dev_attr_group(&spu_attribute_group); | |
| cpu_add_dev_attr_group(&ppe_attribute_group); | |
| } | |
| return rc; | |
| } | |
| module_init(thermal_init); | |
| static void __exit thermal_exit(void) | |
| { | |
| spu_remove_dev_attr_group(&spu_attribute_group); | |
| cpu_remove_dev_attr_group(&ppe_attribute_group); | |
| } | |
| module_exit(thermal_exit); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_AUTHOR("Christian Krafft <krafft@de.ibm.com>"); | |