Path: blob/master/drivers/macintosh/windfarm_smu_sensors.c
15111 views
/*1* Windfarm PowerMac thermal control. SMU based sensors2*3* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.4* <[email protected]>5*6* Released under the term of the GNU GPL v2.7*/89#include <linux/types.h>10#include <linux/errno.h>11#include <linux/kernel.h>12#include <linux/delay.h>13#include <linux/slab.h>14#include <linux/init.h>15#include <linux/wait.h>16#include <linux/completion.h>17#include <asm/prom.h>18#include <asm/machdep.h>19#include <asm/io.h>20#include <asm/system.h>21#include <asm/sections.h>22#include <asm/smu.h>2324#include "windfarm.h"2526#define VERSION "0.2"2728#undef DEBUG2930#ifdef DEBUG31#define DBG(args...) printk(args)32#else33#define DBG(args...) do { } while(0)34#endif3536/*37* Various SMU "partitions" calibration objects for which we38* keep pointers here for use by bits & pieces of the driver39*/40static struct smu_sdbp_cpuvcp *cpuvcp;41static int cpuvcp_version;42static struct smu_sdbp_cpudiode *cpudiode;43static struct smu_sdbp_slotspow *slotspow;44static u8 *debugswitches;4546/*47* SMU basic sensors objects48*/4950static LIST_HEAD(smu_ads);5152struct smu_ad_sensor {53struct list_head link;54u32 reg; /* index in SMU */55struct wf_sensor sens;56};57#define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens)5859static void smu_ads_release(struct wf_sensor *sr)60{61struct smu_ad_sensor *ads = to_smu_ads(sr);6263kfree(ads);64}6566static int smu_read_adc(u8 id, s32 *value)67{68struct smu_simple_cmd cmd;69DECLARE_COMPLETION_ONSTACK(comp);70int rc;7172rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1,73smu_done_complete, &comp, id);74if (rc)75return rc;76wait_for_completion(&comp);77if (cmd.cmd.status != 0)78return cmd.cmd.status;79if (cmd.cmd.reply_len != 2) {80printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n",81id, cmd.cmd.reply_len);82return -EIO;83}84*value = *((u16 *)cmd.buffer);85return 0;86}8788static int smu_cputemp_get(struct wf_sensor *sr, s32 *value)89{90struct smu_ad_sensor *ads = to_smu_ads(sr);91int rc;92s32 val;93s64 scaled;9495rc = smu_read_adc(ads->reg, &val);96if (rc) {97printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n",98rc);99return rc;100}101102/* Ok, we have to scale & adjust, taking units into account */103scaled = (s64)(((u64)val) * (u64)cpudiode->m_value);104scaled >>= 3;105scaled += ((s64)cpudiode->b_value) << 9;106*value = (s32)(scaled << 1);107108return 0;109}110111static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value)112{113struct smu_ad_sensor *ads = to_smu_ads(sr);114s32 val, scaled;115int rc;116117rc = smu_read_adc(ads->reg, &val);118if (rc) {119printk(KERN_ERR "windfarm: read CPU current failed, err %d\n",120rc);121return rc;122}123124/* Ok, we have to scale & adjust, taking units into account */125scaled = (s32)(val * (u32)cpuvcp->curr_scale);126scaled += (s32)cpuvcp->curr_offset;127*value = scaled << 4;128129return 0;130}131132static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value)133{134struct smu_ad_sensor *ads = to_smu_ads(sr);135s32 val, scaled;136int rc;137138rc = smu_read_adc(ads->reg, &val);139if (rc) {140printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n",141rc);142return rc;143}144145/* Ok, we have to scale & adjust, taking units into account */146scaled = (s32)(val * (u32)cpuvcp->volt_scale);147scaled += (s32)cpuvcp->volt_offset;148*value = scaled << 4;149150return 0;151}152153static int smu_slotspow_get(struct wf_sensor *sr, s32 *value)154{155struct smu_ad_sensor *ads = to_smu_ads(sr);156s32 val, scaled;157int rc;158159rc = smu_read_adc(ads->reg, &val);160if (rc) {161printk(KERN_ERR "windfarm: read slots power failed, err %d\n",162rc);163return rc;164}165166/* Ok, we have to scale & adjust, taking units into account */167scaled = (s32)(val * (u32)slotspow->pow_scale);168scaled += (s32)slotspow->pow_offset;169*value = scaled << 4;170171return 0;172}173174175static struct wf_sensor_ops smu_cputemp_ops = {176.get_value = smu_cputemp_get,177.release = smu_ads_release,178.owner = THIS_MODULE,179};180static struct wf_sensor_ops smu_cpuamp_ops = {181.get_value = smu_cpuamp_get,182.release = smu_ads_release,183.owner = THIS_MODULE,184};185static struct wf_sensor_ops smu_cpuvolt_ops = {186.get_value = smu_cpuvolt_get,187.release = smu_ads_release,188.owner = THIS_MODULE,189};190static struct wf_sensor_ops smu_slotspow_ops = {191.get_value = smu_slotspow_get,192.release = smu_ads_release,193.owner = THIS_MODULE,194};195196197static struct smu_ad_sensor *smu_ads_create(struct device_node *node)198{199struct smu_ad_sensor *ads;200const char *c, *l;201const u32 *v;202203ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL);204if (ads == NULL)205return NULL;206c = of_get_property(node, "device_type", NULL);207l = of_get_property(node, "location", NULL);208if (c == NULL || l == NULL)209goto fail;210211/* We currently pick the sensors based on the OF name and location212* properties, while Darwin uses the sensor-id's.213* The problem with the IDs is that they are model specific while it214* looks like apple has been doing a reasonably good job at keeping215* the names and locations consistents so I'll stick with the names216* and locations for now.217*/218if (!strcmp(c, "temp-sensor") &&219!strcmp(l, "CPU T-Diode")) {220ads->sens.ops = &smu_cputemp_ops;221ads->sens.name = "cpu-temp";222if (cpudiode == NULL) {223DBG("wf: cpudiode partition (%02x) not found\n",224SMU_SDB_CPUDIODE_ID);225goto fail;226}227} else if (!strcmp(c, "current-sensor") &&228!strcmp(l, "CPU Current")) {229ads->sens.ops = &smu_cpuamp_ops;230ads->sens.name = "cpu-current";231if (cpuvcp == NULL) {232DBG("wf: cpuvcp partition (%02x) not found\n",233SMU_SDB_CPUVCP_ID);234goto fail;235}236} else if (!strcmp(c, "voltage-sensor") &&237!strcmp(l, "CPU Voltage")) {238ads->sens.ops = &smu_cpuvolt_ops;239ads->sens.name = "cpu-voltage";240if (cpuvcp == NULL) {241DBG("wf: cpuvcp partition (%02x) not found\n",242SMU_SDB_CPUVCP_ID);243goto fail;244}245} else if (!strcmp(c, "power-sensor") &&246!strcmp(l, "Slots Power")) {247ads->sens.ops = &smu_slotspow_ops;248ads->sens.name = "slots-power";249if (slotspow == NULL) {250DBG("wf: slotspow partition (%02x) not found\n",251SMU_SDB_SLOTSPOW_ID);252goto fail;253}254} else255goto fail;256257v = of_get_property(node, "reg", NULL);258if (v == NULL)259goto fail;260ads->reg = *v;261262if (wf_register_sensor(&ads->sens))263goto fail;264return ads;265fail:266kfree(ads);267return NULL;268}269270/*271* SMU Power combo sensor object272*/273274struct smu_cpu_power_sensor {275struct list_head link;276struct wf_sensor *volts;277struct wf_sensor *amps;278int fake_volts : 1;279int quadratic : 1;280struct wf_sensor sens;281};282#define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens)283284static struct smu_cpu_power_sensor *smu_cpu_power;285286static void smu_cpu_power_release(struct wf_sensor *sr)287{288struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);289290if (pow->volts)291wf_put_sensor(pow->volts);292if (pow->amps)293wf_put_sensor(pow->amps);294kfree(pow);295}296297static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value)298{299struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);300s32 volts, amps, power;301u64 tmps, tmpa, tmpb;302int rc;303304rc = pow->amps->ops->get_value(pow->amps, &s);305if (rc)306return rc;307308if (pow->fake_volts) {309*value = amps * 12 - 0x30000;310return 0;311}312313rc = pow->volts->ops->get_value(pow->volts, &volts);314if (rc)315return rc;316317power = (s32)((((u64)volts) * ((u64)amps)) >> 16);318if (!pow->quadratic) {319*value = power;320return 0;321}322tmps = (((u64)power) * ((u64)power)) >> 16;323tmpa = ((u64)cpuvcp->power_quads[0]) * tmps;324tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power);325*value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12);326327return 0;328}329330static struct wf_sensor_ops smu_cpu_power_ops = {331.get_value = smu_cpu_power_get,332.release = smu_cpu_power_release,333.owner = THIS_MODULE,334};335336337static struct smu_cpu_power_sensor *338smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)339{340struct smu_cpu_power_sensor *pow;341342pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);343if (pow == NULL)344return NULL;345pow->sens.ops = &smu_cpu_power_ops;346pow->sens.name = "cpu-power";347348wf_get_sensor(volts);349pow->volts = volts;350wf_get_sensor(amps);351pow->amps = amps;352353/* Some early machines need a faked voltage */354if (debugswitches && ((*debugswitches) & 0x80)) {355printk(KERN_INFO "windfarm: CPU Power sensor using faked"356" voltage !\n");357pow->fake_volts = 1;358} else359pow->fake_volts = 0;360361/* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now,362* I yet have to figure out what's up with 8,2 and will have to363* adjust for later, unless we can 100% trust the SDB partition...364*/365if ((of_machine_is_compatible("PowerMac8,1") ||366of_machine_is_compatible("PowerMac8,2") ||367of_machine_is_compatible("PowerMac9,1")) &&368cpuvcp_version >= 2) {369pow->quadratic = 1;370DBG("windfarm: CPU Power using quadratic transform\n");371} else372pow->quadratic = 0;373374if (wf_register_sensor(&pow->sens))375goto fail;376return pow;377fail:378kfree(pow);379return NULL;380}381382static void smu_fetch_param_partitions(void)383{384const struct smu_sdbp_header *hdr;385386/* Get CPU voltage/current/power calibration data */387hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);388if (hdr != NULL) {389cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];390/* Keep version around */391cpuvcp_version = hdr->version;392}393394/* Get CPU diode calibration data */395hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);396if (hdr != NULL)397cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];398399/* Get slots power calibration data if any */400hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);401if (hdr != NULL)402slotspow = (struct smu_sdbp_slotspow *)&hdr[1];403404/* Get debug switches if any */405hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);406if (hdr != NULL)407debugswitches = (u8 *)&hdr[1];408}409410static int __init smu_sensors_init(void)411{412struct device_node *smu, *sensors, *s;413struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;414415if (!smu_present())416return -ENODEV;417418/* Get parameters partitions */419smu_fetch_param_partitions();420421smu = of_find_node_by_type(NULL, "smu");422if (smu == NULL)423return -ENODEV;424425/* Look for sensors subdir */426for (sensors = NULL;427(sensors = of_get_next_child(smu, sensors)) != NULL;)428if (!strcmp(sensors->name, "sensors"))429break;430431of_node_put(smu);432433/* Create basic sensors */434for (s = NULL;435sensors && (s = of_get_next_child(sensors, s)) != NULL;) {436struct smu_ad_sensor *ads;437438ads = smu_ads_create(s);439if (ads == NULL)440continue;441list_add(&ads->link, &smu_ads);442/* keep track of cpu voltage & current */443if (!strcmp(ads->sens.name, "cpu-voltage"))444volt_sensor = ads;445else if (!strcmp(ads->sens.name, "cpu-current"))446curr_sensor = ads;447}448449of_node_put(sensors);450451/* Create CPU power sensor if possible */452if (volt_sensor && curr_sensor)453smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,454&curr_sensor->sens);455456return 0;457}458459static void __exit smu_sensors_exit(void)460{461struct smu_ad_sensor *ads;462463/* dispose of power sensor */464if (smu_cpu_power)465wf_unregister_sensor(&smu_cpu_power->sens);466467/* dispose of basic sensors */468while (!list_empty(&smu_ads)) {469ads = list_entry(smu_ads.next, struct smu_ad_sensor, link);470list_del(&ads->link);471wf_unregister_sensor(&ads->sens);472}473}474475476module_init(smu_sensors_init);477module_exit(smu_sensors_exit);478479MODULE_AUTHOR("Benjamin Herrenschmidt <[email protected]>");480MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");481MODULE_LICENSE("GPL");482483484485