362 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Windfarm PowerMac thermal control. SMU "satellite" controller sensors.4 *5 * Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org>6 */7 8#include <linux/types.h>9#include <linux/errno.h>10#include <linux/kernel.h>11#include <linux/slab.h>12#include <linux/init.h>13#include <linux/wait.h>14#include <linux/i2c.h>15#include <linux/mutex.h>16 17#include <asm/smu.h>18#include <asm/pmac_low_i2c.h>19 20#include "windfarm.h"21 22#define VERSION "1.0"23 24/* If the cache is older than 800ms we'll refetch it */25#define MAX_AGE msecs_to_jiffies(800)26 27struct wf_sat {28 struct kref ref;29 int nr;30 struct mutex mutex;31 unsigned long last_read; /* jiffies when cache last updated */32 u8 cache[16];33 struct list_head sensors;34 struct i2c_client *i2c;35 struct device_node *node;36};37 38static struct wf_sat *sats[2];39 40struct wf_sat_sensor {41 struct list_head link;42 int index;43 int index2; /* used for power sensors */44 int shift;45 struct wf_sat *sat;46 struct wf_sensor sens;47};48 49#define wf_to_sat(c) container_of(c, struct wf_sat_sensor, sens)50 51struct smu_sdbp_header *smu_sat_get_sdb_partition(unsigned int sat_id, int id,52 unsigned int *size)53{54 struct wf_sat *sat;55 int err;56 unsigned int i, len;57 u8 *buf;58 u8 data[4];59 60 /* TODO: Add the resulting partition to the device-tree */61 62 if (sat_id > 1 || (sat = sats[sat_id]) == NULL)63 return NULL;64 65 err = i2c_smbus_write_word_data(sat->i2c, 8, id << 8);66 if (err) {67 printk(KERN_ERR "smu_sat_get_sdb_part wr error %d\n", err);68 return NULL;69 }70 71 err = i2c_smbus_read_word_data(sat->i2c, 9);72 if (err < 0) {73 printk(KERN_ERR "smu_sat_get_sdb_part rd len error\n");74 return NULL;75 }76 len = err;77 if (len == 0) {78 printk(KERN_ERR "smu_sat_get_sdb_part no partition %x\n", id);79 return NULL;80 }81 82 len = le16_to_cpu(len);83 len = (len + 3) & ~3;84 buf = kmalloc(len, GFP_KERNEL);85 if (buf == NULL)86 return NULL;87 88 for (i = 0; i < len; i += 4) {89 err = i2c_smbus_read_i2c_block_data(sat->i2c, 0xa, 4, data);90 if (err < 0) {91 printk(KERN_ERR "smu_sat_get_sdb_part rd err %d\n",92 err);93 goto fail;94 }95 buf[i] = data[1];96 buf[i+1] = data[0];97 buf[i+2] = data[3];98 buf[i+3] = data[2];99 }100 101 printk(KERN_DEBUG "sat %d partition %x:", sat_id, id);102 print_hex_dump(KERN_DEBUG, " ", DUMP_PREFIX_OFFSET,103 16, 1, buf, len, false);104 if (size)105 *size = len;106 return (struct smu_sdbp_header *) buf;107 108 fail:109 kfree(buf);110 return NULL;111}112EXPORT_SYMBOL_GPL(smu_sat_get_sdb_partition);113 114/* refresh the cache */115static int wf_sat_read_cache(struct wf_sat *sat)116{117 int err;118 119 err = i2c_smbus_read_i2c_block_data(sat->i2c, 0x3f, 16, sat->cache);120 if (err < 0)121 return err;122 sat->last_read = jiffies;123 124#ifdef LOTSA_DEBUG125 {126 int i;127 printk(KERN_DEBUG "wf_sat_get: data is");128 print_hex_dump(KERN_DEBUG, " ", DUMP_PREFIX_OFFSET,129 16, 1, sat->cache, 16, false);130 }131#endif132 return 0;133}134 135static int wf_sat_sensor_get(struct wf_sensor *sr, s32 *value)136{137 struct wf_sat_sensor *sens = wf_to_sat(sr);138 struct wf_sat *sat = sens->sat;139 int i, err;140 s32 val;141 142 if (sat->i2c == NULL)143 return -ENODEV;144 145 mutex_lock(&sat->mutex);146 if (time_after(jiffies, (sat->last_read + MAX_AGE))) {147 err = wf_sat_read_cache(sat);148 if (err)149 goto fail;150 }151 152 i = sens->index * 2;153 val = ((sat->cache[i] << 8) + sat->cache[i+1]) << sens->shift;154 if (sens->index2 >= 0) {155 i = sens->index2 * 2;156 /* 4.12 * 8.8 -> 12.20; shift right 4 to get 16.16 */157 val = (val * ((sat->cache[i] << 8) + sat->cache[i+1])) >> 4;158 }159 160 *value = val;161 err = 0;162 163 fail:164 mutex_unlock(&sat->mutex);165 return err;166}167 168static void wf_sat_release(struct kref *ref)169{170 struct wf_sat *sat = container_of(ref, struct wf_sat, ref);171 172 if (sat->nr >= 0)173 sats[sat->nr] = NULL;174 of_node_put(sat->node);175 kfree(sat);176}177 178static void wf_sat_sensor_release(struct wf_sensor *sr)179{180 struct wf_sat_sensor *sens = wf_to_sat(sr);181 struct wf_sat *sat = sens->sat;182 183 kfree(sens);184 kref_put(&sat->ref, wf_sat_release);185}186 187static const struct wf_sensor_ops wf_sat_ops = {188 .get_value = wf_sat_sensor_get,189 .release = wf_sat_sensor_release,190 .owner = THIS_MODULE,191};192 193static int wf_sat_probe(struct i2c_client *client)194{195 struct device_node *dev = client->dev.of_node;196 struct wf_sat *sat;197 struct wf_sat_sensor *sens;198 const u32 *reg;199 const char *loc;200 u8 chip, core;201 struct device_node *child;202 int shift, cpu, index;203 char *name;204 int vsens[2], isens[2];205 206 sat = kzalloc(sizeof(struct wf_sat), GFP_KERNEL);207 if (sat == NULL)208 return -ENOMEM;209 sat->nr = -1;210 sat->node = of_node_get(dev);211 kref_init(&sat->ref);212 mutex_init(&sat->mutex);213 sat->i2c = client;214 INIT_LIST_HEAD(&sat->sensors);215 i2c_set_clientdata(client, sat);216 217 vsens[0] = vsens[1] = -1;218 isens[0] = isens[1] = -1;219 for_each_child_of_node(dev, child) {220 reg = of_get_property(child, "reg", NULL);221 loc = of_get_property(child, "location", NULL);222 if (reg == NULL || loc == NULL)223 continue;224 225 /* the cooked sensors are between 0x30 and 0x37 */226 if (*reg < 0x30 || *reg > 0x37)227 continue;228 index = *reg - 0x30;229 230 /* expect location to be CPU [AB][01] ... */231 if (strncmp(loc, "CPU ", 4) != 0)232 continue;233 chip = loc[4] - 'A';234 core = loc[5] - '0';235 if (chip > 1 || core > 1) {236 printk(KERN_ERR "wf_sat_create: don't understand "237 "location %s for %pOF\n", loc, child);238 continue;239 }240 cpu = 2 * chip + core;241 if (sat->nr < 0)242 sat->nr = chip;243 else if (sat->nr != chip) {244 printk(KERN_ERR "wf_sat_create: can't cope with "245 "multiple CPU chips on one SAT (%s)\n", loc);246 continue;247 }248 249 if (of_node_is_type(child, "voltage-sensor")) {250 name = "cpu-voltage";251 shift = 4;252 vsens[core] = index;253 } else if (of_node_is_type(child, "current-sensor")) {254 name = "cpu-current";255 shift = 8;256 isens[core] = index;257 } else if (of_node_is_type(child, "temp-sensor")) {258 name = "cpu-temp";259 shift = 10;260 } else261 continue; /* hmmm shouldn't happen */262 263 /* the +16 is enough for "cpu-voltage-n" */264 sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL);265 if (sens == NULL) {266 printk(KERN_ERR "wf_sat_create: couldn't create "267 "%s sensor %d (no memory)\n", name, cpu);268 continue;269 }270 sens->index = index;271 sens->index2 = -1;272 sens->shift = shift;273 sens->sat = sat;274 sens->sens.ops = &wf_sat_ops;275 sens->sens.name = (char *) (sens + 1);276 snprintf((char *)sens->sens.name, 16, "%s-%d", name, cpu);277 278 if (wf_register_sensor(&sens->sens))279 kfree(sens);280 else {281 list_add(&sens->link, &sat->sensors);282 kref_get(&sat->ref);283 }284 }285 286 /* make the power sensors */287 for (core = 0; core < 2; ++core) {288 if (vsens[core] < 0 || isens[core] < 0)289 continue;290 cpu = 2 * sat->nr + core;291 sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL);292 if (sens == NULL) {293 printk(KERN_ERR "wf_sat_create: couldn't create power "294 "sensor %d (no memory)\n", cpu);295 continue;296 }297 sens->index = vsens[core];298 sens->index2 = isens[core];299 sens->shift = 0;300 sens->sat = sat;301 sens->sens.ops = &wf_sat_ops;302 sens->sens.name = (char *) (sens + 1);303 snprintf((char *)sens->sens.name, 16, "cpu-power-%d", cpu);304 305 if (wf_register_sensor(&sens->sens))306 kfree(sens);307 else {308 list_add(&sens->link, &sat->sensors);309 kref_get(&sat->ref);310 }311 }312 313 if (sat->nr >= 0)314 sats[sat->nr] = sat;315 316 return 0;317}318 319static void wf_sat_remove(struct i2c_client *client)320{321 struct wf_sat *sat = i2c_get_clientdata(client);322 struct wf_sat_sensor *sens;323 324 /* release sensors */325 while(!list_empty(&sat->sensors)) {326 sens = list_first_entry(&sat->sensors,327 struct wf_sat_sensor, link);328 list_del(&sens->link);329 wf_unregister_sensor(&sens->sens);330 }331 sat->i2c = NULL;332 kref_put(&sat->ref, wf_sat_release);333}334 335static const struct i2c_device_id wf_sat_id[] = {336 { "MAC,smu-sat" },337 { }338};339MODULE_DEVICE_TABLE(i2c, wf_sat_id);340 341static const struct of_device_id wf_sat_of_id[] = {342 { .compatible = "smu-sat", },343 { }344};345MODULE_DEVICE_TABLE(of, wf_sat_of_id);346 347static struct i2c_driver wf_sat_driver = {348 .driver = {349 .name = "wf_smu_sat",350 .of_match_table = wf_sat_of_id,351 },352 .probe = wf_sat_probe,353 .remove = wf_sat_remove,354 .id_table = wf_sat_id,355};356 357module_i2c_driver(wf_sat_driver);358 359MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");360MODULE_DESCRIPTION("SMU satellite sensors for PowerMac thermal control");361MODULE_LICENSE("GPL");362