Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Windfarm PowerMac thermal control. SMU based sensors
0004  *
0005  * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
0006  *                    <benh@kernel.crashing.org>
0007  */
0008 
0009 #include <linux/types.h>
0010 #include <linux/errno.h>
0011 #include <linux/kernel.h>
0012 #include <linux/delay.h>
0013 #include <linux/slab.h>
0014 #include <linux/init.h>
0015 #include <linux/wait.h>
0016 #include <linux/completion.h>
0017 #include <linux/of.h>
0018 
0019 #include <asm/machdep.h>
0020 #include <asm/io.h>
0021 #include <asm/sections.h>
0022 #include <asm/smu.h>
0023 
0024 #include "windfarm.h"
0025 
0026 #define VERSION "0.2"
0027 
0028 #undef DEBUG
0029 
0030 #ifdef DEBUG
0031 #define DBG(args...)    printk(args)
0032 #else
0033 #define DBG(args...)    do { } while(0)
0034 #endif
0035 
0036 /*
0037  * Various SMU "partitions" calibration objects for which we
0038  * keep pointers here for use by bits & pieces of the driver
0039  */
0040 static struct smu_sdbp_cpuvcp *cpuvcp;
0041 static int  cpuvcp_version;
0042 static struct smu_sdbp_cpudiode *cpudiode;
0043 static struct smu_sdbp_slotspow *slotspow;
0044 static u8 *debugswitches;
0045 
0046 /*
0047  * SMU basic sensors objects
0048  */
0049 
0050 static LIST_HEAD(smu_ads);
0051 
0052 struct smu_ad_sensor {
0053     struct list_head    link;
0054     u32         reg;        /* index in SMU */
0055     struct wf_sensor    sens;
0056 };
0057 #define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens)
0058 
0059 static void smu_ads_release(struct wf_sensor *sr)
0060 {
0061     struct smu_ad_sensor *ads = to_smu_ads(sr);
0062 
0063     kfree(ads);
0064 }
0065 
0066 static int smu_read_adc(u8 id, s32 *value)
0067 {
0068     struct smu_simple_cmd   cmd;
0069     DECLARE_COMPLETION_ONSTACK(comp);
0070     int rc;
0071 
0072     rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1,
0073                   smu_done_complete, &comp, id);
0074     if (rc)
0075         return rc;
0076     wait_for_completion(&comp);
0077     if (cmd.cmd.status != 0)
0078         return cmd.cmd.status;
0079     if (cmd.cmd.reply_len != 2) {
0080         printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n",
0081                id, cmd.cmd.reply_len);
0082         return -EIO;
0083     }
0084     *value = *((u16 *)cmd.buffer);
0085     return 0;
0086 }
0087 
0088 static int smu_cputemp_get(struct wf_sensor *sr, s32 *value)
0089 {
0090     struct smu_ad_sensor *ads = to_smu_ads(sr);
0091     int rc;
0092     s32 val;
0093     s64 scaled;
0094 
0095     rc = smu_read_adc(ads->reg, &val);
0096     if (rc) {
0097         printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n",
0098                rc);
0099         return rc;
0100     }
0101 
0102     /* Ok, we have to scale & adjust, taking units into account */
0103     scaled = (s64)(((u64)val) * (u64)cpudiode->m_value);
0104     scaled >>= 3;
0105     scaled += ((s64)cpudiode->b_value) << 9;
0106     *value = (s32)(scaled << 1);
0107 
0108     return 0;
0109 }
0110 
0111 static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value)
0112 {
0113     struct smu_ad_sensor *ads = to_smu_ads(sr);
0114     s32 val, scaled;
0115     int rc;
0116 
0117     rc = smu_read_adc(ads->reg, &val);
0118     if (rc) {
0119         printk(KERN_ERR "windfarm: read CPU current failed, err %d\n",
0120                rc);
0121         return rc;
0122     }
0123 
0124     /* Ok, we have to scale & adjust, taking units into account */
0125     scaled = (s32)(val * (u32)cpuvcp->curr_scale);
0126     scaled += (s32)cpuvcp->curr_offset;
0127     *value = scaled << 4;
0128 
0129     return 0;
0130 }
0131 
0132 static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value)
0133 {
0134     struct smu_ad_sensor *ads = to_smu_ads(sr);
0135     s32 val, scaled;
0136     int rc;
0137 
0138     rc = smu_read_adc(ads->reg, &val);
0139     if (rc) {
0140         printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n",
0141                rc);
0142         return rc;
0143     }
0144 
0145     /* Ok, we have to scale & adjust, taking units into account */
0146     scaled = (s32)(val * (u32)cpuvcp->volt_scale);
0147     scaled += (s32)cpuvcp->volt_offset;
0148     *value = scaled << 4;
0149 
0150     return 0;
0151 }
0152 
0153 static int smu_slotspow_get(struct wf_sensor *sr, s32 *value)
0154 {
0155     struct smu_ad_sensor *ads = to_smu_ads(sr);
0156     s32 val, scaled;
0157     int rc;
0158 
0159     rc = smu_read_adc(ads->reg, &val);
0160     if (rc) {
0161         printk(KERN_ERR "windfarm: read slots power failed, err %d\n",
0162                rc);
0163         return rc;
0164     }
0165 
0166     /* Ok, we have to scale & adjust, taking units into account */
0167     scaled = (s32)(val * (u32)slotspow->pow_scale);
0168     scaled += (s32)slotspow->pow_offset;
0169     *value = scaled << 4;
0170 
0171     return 0;
0172 }
0173 
0174 
0175 static const struct wf_sensor_ops smu_cputemp_ops = {
0176     .get_value  = smu_cputemp_get,
0177     .release    = smu_ads_release,
0178     .owner      = THIS_MODULE,
0179 };
0180 static const struct wf_sensor_ops smu_cpuamp_ops = {
0181     .get_value  = smu_cpuamp_get,
0182     .release    = smu_ads_release,
0183     .owner      = THIS_MODULE,
0184 };
0185 static const struct wf_sensor_ops smu_cpuvolt_ops = {
0186     .get_value  = smu_cpuvolt_get,
0187     .release    = smu_ads_release,
0188     .owner      = THIS_MODULE,
0189 };
0190 static const struct wf_sensor_ops smu_slotspow_ops = {
0191     .get_value  = smu_slotspow_get,
0192     .release    = smu_ads_release,
0193     .owner      = THIS_MODULE,
0194 };
0195 
0196 
0197 static struct smu_ad_sensor *smu_ads_create(struct device_node *node)
0198 {
0199     struct smu_ad_sensor *ads;
0200     const char *l;
0201     const u32 *v;
0202 
0203     ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL);
0204     if (ads == NULL)
0205         return NULL;
0206     l = of_get_property(node, "location", NULL);
0207     if (l == NULL)
0208         goto fail;
0209 
0210     /* We currently pick the sensors based on the OF name and location
0211      * properties, while Darwin uses the sensor-id's.
0212      * The problem with the IDs is that they are model specific while it
0213      * looks like apple has been doing a reasonably good job at keeping
0214      * the names and locations consistents so I'll stick with the names
0215      * and locations for now.
0216      */
0217     if (of_node_is_type(node, "temp-sensor") &&
0218         !strcmp(l, "CPU T-Diode")) {
0219         ads->sens.ops = &smu_cputemp_ops;
0220         ads->sens.name = "cpu-temp";
0221         if (cpudiode == NULL) {
0222             DBG("wf: cpudiode partition (%02x) not found\n",
0223                 SMU_SDB_CPUDIODE_ID);
0224             goto fail;
0225         }
0226     } else if (of_node_is_type(node, "current-sensor") &&
0227            !strcmp(l, "CPU Current")) {
0228         ads->sens.ops = &smu_cpuamp_ops;
0229         ads->sens.name = "cpu-current";
0230         if (cpuvcp == NULL) {
0231             DBG("wf: cpuvcp partition (%02x) not found\n",
0232                 SMU_SDB_CPUVCP_ID);
0233             goto fail;
0234         }
0235     } else if (of_node_is_type(node, "voltage-sensor") &&
0236            !strcmp(l, "CPU Voltage")) {
0237         ads->sens.ops = &smu_cpuvolt_ops;
0238         ads->sens.name = "cpu-voltage";
0239         if (cpuvcp == NULL) {
0240             DBG("wf: cpuvcp partition (%02x) not found\n",
0241                 SMU_SDB_CPUVCP_ID);
0242             goto fail;
0243         }
0244     } else if (of_node_is_type(node, "power-sensor") &&
0245            !strcmp(l, "Slots Power")) {
0246         ads->sens.ops = &smu_slotspow_ops;
0247         ads->sens.name = "slots-power";
0248         if (slotspow == NULL) {
0249             DBG("wf: slotspow partition (%02x) not found\n",
0250                 SMU_SDB_SLOTSPOW_ID);
0251             goto fail;
0252         }
0253     } else
0254         goto fail;
0255 
0256     v = of_get_property(node, "reg", NULL);
0257     if (v == NULL)
0258         goto fail;
0259     ads->reg = *v;
0260 
0261     if (wf_register_sensor(&ads->sens))
0262         goto fail;
0263     return ads;
0264  fail:
0265     kfree(ads);
0266     return NULL;
0267 }
0268 
0269 /*
0270  * SMU Power combo sensor object
0271  */
0272 
0273 struct smu_cpu_power_sensor {
0274     struct list_head    link;
0275     struct wf_sensor    *volts;
0276     struct wf_sensor    *amps;
0277     int         fake_volts : 1;
0278     int         quadratic : 1;
0279     struct wf_sensor    sens;
0280 };
0281 #define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens)
0282 
0283 static struct smu_cpu_power_sensor *smu_cpu_power;
0284 
0285 static void smu_cpu_power_release(struct wf_sensor *sr)
0286 {
0287     struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
0288 
0289     if (pow->volts)
0290         wf_put_sensor(pow->volts);
0291     if (pow->amps)
0292         wf_put_sensor(pow->amps);
0293     kfree(pow);
0294 }
0295 
0296 static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value)
0297 {
0298     struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
0299     s32 volts, amps, power;
0300     u64 tmps, tmpa, tmpb;
0301     int rc;
0302 
0303     rc = pow->amps->ops->get_value(pow->amps, &amps);
0304     if (rc)
0305         return rc;
0306 
0307     if (pow->fake_volts) {
0308         *value = amps * 12 - 0x30000;
0309         return 0;
0310     }
0311 
0312     rc = pow->volts->ops->get_value(pow->volts, &volts);
0313     if (rc)
0314         return rc;
0315 
0316     power = (s32)((((u64)volts) * ((u64)amps)) >> 16);
0317     if (!pow->quadratic) {
0318         *value = power;
0319         return 0;
0320     }
0321     tmps = (((u64)power) * ((u64)power)) >> 16;
0322     tmpa = ((u64)cpuvcp->power_quads[0]) * tmps;
0323     tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power);
0324     *value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12);
0325 
0326     return 0;
0327 }
0328 
0329 static const struct wf_sensor_ops smu_cpu_power_ops = {
0330     .get_value  = smu_cpu_power_get,
0331     .release    = smu_cpu_power_release,
0332     .owner      = THIS_MODULE,
0333 };
0334 
0335 
0336 static struct smu_cpu_power_sensor *
0337 smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)
0338 {
0339     struct smu_cpu_power_sensor *pow;
0340 
0341     pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);
0342     if (pow == NULL)
0343         return NULL;
0344     pow->sens.ops = &smu_cpu_power_ops;
0345     pow->sens.name = "cpu-power";
0346 
0347     wf_get_sensor(volts);
0348     pow->volts = volts;
0349     wf_get_sensor(amps);
0350     pow->amps = amps;
0351 
0352     /* Some early machines need a faked voltage */
0353     if (debugswitches && ((*debugswitches) & 0x80)) {
0354         printk(KERN_INFO "windfarm: CPU Power sensor using faked"
0355                " voltage !\n");
0356         pow->fake_volts = 1;
0357     } else
0358         pow->fake_volts = 0;
0359 
0360     /* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now,
0361      * I yet have to figure out what's up with 8,2 and will have to
0362      * adjust for later, unless we can 100% trust the SDB partition...
0363      */
0364     if ((of_machine_is_compatible("PowerMac8,1") ||
0365          of_machine_is_compatible("PowerMac8,2") ||
0366          of_machine_is_compatible("PowerMac9,1")) &&
0367         cpuvcp_version >= 2) {
0368         pow->quadratic = 1;
0369         DBG("windfarm: CPU Power using quadratic transform\n");
0370     } else
0371         pow->quadratic = 0;
0372 
0373     if (wf_register_sensor(&pow->sens))
0374         goto fail;
0375     return pow;
0376  fail:
0377     kfree(pow);
0378     return NULL;
0379 }
0380 
0381 static void smu_fetch_param_partitions(void)
0382 {
0383     const struct smu_sdbp_header *hdr;
0384 
0385     /* Get CPU voltage/current/power calibration data */
0386     hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);
0387     if (hdr != NULL) {
0388         cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];
0389         /* Keep version around */
0390         cpuvcp_version = hdr->version;
0391     }
0392 
0393     /* Get CPU diode calibration data */
0394     hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);
0395     if (hdr != NULL)
0396         cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];
0397 
0398     /* Get slots power calibration data if any */
0399     hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);
0400     if (hdr != NULL)
0401         slotspow = (struct smu_sdbp_slotspow *)&hdr[1];
0402 
0403     /* Get debug switches if any */
0404     hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);
0405     if (hdr != NULL)
0406         debugswitches = (u8 *)&hdr[1];
0407 }
0408 
0409 static int __init smu_sensors_init(void)
0410 {
0411     struct device_node *smu, *sensors, *s;
0412     struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;
0413 
0414     if (!smu_present())
0415         return -ENODEV;
0416 
0417     /* Get parameters partitions */
0418     smu_fetch_param_partitions();
0419 
0420     smu = of_find_node_by_type(NULL, "smu");
0421     if (smu == NULL)
0422         return -ENODEV;
0423 
0424     /* Look for sensors subdir */
0425     for_each_child_of_node(smu, sensors)
0426         if (of_node_name_eq(sensors, "sensors"))
0427             break;
0428 
0429     of_node_put(smu);
0430 
0431     /* Create basic sensors */
0432     for (s = NULL;
0433          sensors && (s = of_get_next_child(sensors, s)) != NULL;) {
0434         struct smu_ad_sensor *ads;
0435 
0436         ads = smu_ads_create(s);
0437         if (ads == NULL)
0438             continue;
0439         list_add(&ads->link, &smu_ads);
0440         /* keep track of cpu voltage & current */
0441         if (!strcmp(ads->sens.name, "cpu-voltage"))
0442             volt_sensor = ads;
0443         else if (!strcmp(ads->sens.name, "cpu-current"))
0444             curr_sensor = ads;
0445     }
0446 
0447     of_node_put(sensors);
0448 
0449     /* Create CPU power sensor if possible */
0450     if (volt_sensor && curr_sensor)
0451         smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,
0452                              &curr_sensor->sens);
0453 
0454     return 0;
0455 }
0456 
0457 static void __exit smu_sensors_exit(void)
0458 {
0459     struct smu_ad_sensor *ads;
0460 
0461     /* dispose of power sensor */
0462     if (smu_cpu_power)
0463         wf_unregister_sensor(&smu_cpu_power->sens);
0464 
0465     /* dispose of basic sensors */
0466     while (!list_empty(&smu_ads)) {
0467         ads = list_entry(smu_ads.next, struct smu_ad_sensor, link);
0468         list_del(&ads->link);
0469         wf_unregister_sensor(&ads->sens);
0470     }
0471 }
0472 
0473 
0474 module_init(smu_sensors_init);
0475 module_exit(smu_sensors_exit);
0476 
0477 MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
0478 MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");
0479 MODULE_LICENSE("GPL");
0480