| /* |
| * 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 <linux/stringify.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>"); |
| |