linux/drivers/macintosh/windfarm_smu_sat.c

362 lines
8.2 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* Windfarm PowerMac thermal control. SMU "satellite" controller sensors.
*
* Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org>
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <asm/smu.h>
#include <asm/pmac_low_i2c.h>
#include "windfarm.h"
#define VERSION "1.0"
/* If the cache is older than 800ms we'll refetch it */
#define MAX_AGE msecs_to_jiffies(800)
struct wf_sat {
struct kref ref;
int nr;
struct mutex mutex;
unsigned long last_read; /* jiffies when cache last updated */
u8 cache[16];
struct list_head sensors;
struct i2c_client *i2c;
struct device_node *node;
};
static struct wf_sat *sats[2];
struct wf_sat_sensor {
struct list_head link;
int index;
int index2; /* used for power sensors */
int shift;
struct wf_sat *sat;
struct wf_sensor sens;
};
#define wf_to_sat(c) container_of(c, struct wf_sat_sensor, sens)
struct smu_sdbp_header *smu_sat_get_sdb_partition(unsigned int sat_id, int id,
unsigned int *size)
{
struct wf_sat *sat;
int err;
unsigned int i, len;
u8 *buf;
u8 data[4];
/* TODO: Add the resulting partition to the device-tree */
if (sat_id > 1 || (sat = sats[sat_id]) == NULL)
return NULL;
err = i2c_smbus_write_word_data(sat->i2c, 8, id << 8);
if (err) {
printk(KERN_ERR "smu_sat_get_sdb_part wr error %d\n", err);
return NULL;
}
err = i2c_smbus_read_word_data(sat->i2c, 9);
if (err < 0) {
printk(KERN_ERR "smu_sat_get_sdb_part rd len error\n");
return NULL;
}
len = err;
if (len == 0) {
printk(KERN_ERR "smu_sat_get_sdb_part no partition %x\n", id);
return NULL;
}
len = le16_to_cpu(len);
len = (len + 3) & ~3;
buf = kmalloc(len, GFP_KERNEL);
if (buf == NULL)
return NULL;
for (i = 0; i < len; i += 4) {
err = i2c_smbus_read_i2c_block_data(sat->i2c, 0xa, 4, data);
i2c: Fix the i2c_smbus_read_i2c_block_data() prototype Let the drivers specify how many bytes they want to read with i2c_smbus_read_i2c_block_data(). So far, the block count was hard-coded to I2C_SMBUS_BLOCK_MAX (32), which did not make much sense. Many driver authors complained about this before, and I believe it's about time to fix it. Right now, authors have to do technically stupid things, such as individual byte reads or full-fledged I2C messaging, to work around the problem. We do not want to encourage that. I even found that some bus drivers (e.g. i2c-amd8111) already implemented I2C block read the "right" way, that is, they didn't follow the old, broken standard. The fact that it was never noticed before just shows how little i2c_smbus_read_i2c_block_data() was used, which isn't that surprising given how broken its prototype was so far. There are some obvious compatiblity considerations: * This changes the i2c_smbus_read_i2c_block_data() prototype. Users outside the kernel tree will notice at compilation time, and will have to update their code. * User-space has access to i2c_smbus_xfer() directly using i2c-dev, so the changed expectations would affect tools such as i2cdump. In order to preserve binary compatibility, we give I2C_SMBUS_I2C_BLOCK_DATA a new numeric value, and define I2C_SMBUS_I2C_BLOCK_BROKEN with the old numeric value. When i2c-dev receives a transaction with the old value, it can convert it to the new format on the fly. Signed-off-by: Jean Delvare <khali@linux-fr.org>
2007-07-12 16:12:29 +04:00
if (err < 0) {
printk(KERN_ERR "smu_sat_get_sdb_part rd err %d\n",
err);
goto fail;
}
buf[i] = data[1];
buf[i+1] = data[0];
buf[i+2] = data[3];
buf[i+3] = data[2];
}
printk(KERN_DEBUG "sat %d partition %x:", sat_id, id);
print_hex_dump(KERN_DEBUG, " ", DUMP_PREFIX_OFFSET,
16, 1, buf, len, false);
if (size)
*size = len;
return (struct smu_sdbp_header *) buf;
fail:
kfree(buf);
return NULL;
}
EXPORT_SYMBOL_GPL(smu_sat_get_sdb_partition);
/* refresh the cache */
static int wf_sat_read_cache(struct wf_sat *sat)
{
int err;
err = i2c_smbus_read_i2c_block_data(sat->i2c, 0x3f, 16, sat->cache);
i2c: Fix the i2c_smbus_read_i2c_block_data() prototype Let the drivers specify how many bytes they want to read with i2c_smbus_read_i2c_block_data(). So far, the block count was hard-coded to I2C_SMBUS_BLOCK_MAX (32), which did not make much sense. Many driver authors complained about this before, and I believe it's about time to fix it. Right now, authors have to do technically stupid things, such as individual byte reads or full-fledged I2C messaging, to work around the problem. We do not want to encourage that. I even found that some bus drivers (e.g. i2c-amd8111) already implemented I2C block read the "right" way, that is, they didn't follow the old, broken standard. The fact that it was never noticed before just shows how little i2c_smbus_read_i2c_block_data() was used, which isn't that surprising given how broken its prototype was so far. There are some obvious compatiblity considerations: * This changes the i2c_smbus_read_i2c_block_data() prototype. Users outside the kernel tree will notice at compilation time, and will have to update their code. * User-space has access to i2c_smbus_xfer() directly using i2c-dev, so the changed expectations would affect tools such as i2cdump. In order to preserve binary compatibility, we give I2C_SMBUS_I2C_BLOCK_DATA a new numeric value, and define I2C_SMBUS_I2C_BLOCK_BROKEN with the old numeric value. When i2c-dev receives a transaction with the old value, it can convert it to the new format on the fly. Signed-off-by: Jean Delvare <khali@linux-fr.org>
2007-07-12 16:12:29 +04:00
if (err < 0)
return err;
sat->last_read = jiffies;
#ifdef LOTSA_DEBUG
{
int i;
printk(KERN_DEBUG "wf_sat_get: data is");
print_hex_dump(KERN_DEBUG, " ", DUMP_PREFIX_OFFSET,
16, 1, sat->cache, 16, false);
}
#endif
return 0;
}
static int wf_sat_sensor_get(struct wf_sensor *sr, s32 *value)
{
struct wf_sat_sensor *sens = wf_to_sat(sr);
struct wf_sat *sat = sens->sat;
int i, err;
s32 val;
if (sat->i2c == NULL)
return -ENODEV;
mutex_lock(&sat->mutex);
if (time_after(jiffies, (sat->last_read + MAX_AGE))) {
err = wf_sat_read_cache(sat);
if (err)
goto fail;
}
i = sens->index * 2;
val = ((sat->cache[i] << 8) + sat->cache[i+1]) << sens->shift;
if (sens->index2 >= 0) {
i = sens->index2 * 2;
/* 4.12 * 8.8 -> 12.20; shift right 4 to get 16.16 */
val = (val * ((sat->cache[i] << 8) + sat->cache[i+1])) >> 4;
}
*value = val;
err = 0;
fail:
mutex_unlock(&sat->mutex);
return err;
}
static void wf_sat_release(struct kref *ref)
{
struct wf_sat *sat = container_of(ref, struct wf_sat, ref);
if (sat->nr >= 0)
sats[sat->nr] = NULL;
kfree(sat);
}
static void wf_sat_sensor_release(struct wf_sensor *sr)
{
struct wf_sat_sensor *sens = wf_to_sat(sr);
struct wf_sat *sat = sens->sat;
kfree(sens);
kref_put(&sat->ref, wf_sat_release);
}
static const struct wf_sensor_ops wf_sat_ops = {
.get_value = wf_sat_sensor_get,
.release = wf_sat_sensor_release,
.owner = THIS_MODULE,
};
static int wf_sat_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device_node *dev = client->dev.of_node;
struct wf_sat *sat;
struct wf_sat_sensor *sens;
const u32 *reg;
const char *loc;
u8 chip, core;
struct device_node *child;
int shift, cpu, index;
char *name;
int vsens[2], isens[2];
sat = kzalloc(sizeof(struct wf_sat), GFP_KERNEL);
if (sat == NULL)
return -ENOMEM;
sat->nr = -1;
sat->node = of_node_get(dev);
kref_init(&sat->ref);
mutex_init(&sat->mutex);
sat->i2c = client;
INIT_LIST_HEAD(&sat->sensors);
i2c_set_clientdata(client, sat);
vsens[0] = vsens[1] = -1;
isens[0] = isens[1] = -1;
for_each_child_of_node(dev, child) {
reg = of_get_property(child, "reg", NULL);
loc = of_get_property(child, "location", NULL);
if (reg == NULL || loc == NULL)
continue;
/* the cooked sensors are between 0x30 and 0x37 */
if (*reg < 0x30 || *reg > 0x37)
continue;
index = *reg - 0x30;
/* expect location to be CPU [AB][01] ... */
if (strncmp(loc, "CPU ", 4) != 0)
continue;
chip = loc[4] - 'A';
core = loc[5] - '0';
if (chip > 1 || core > 1) {
printk(KERN_ERR "wf_sat_create: don't understand "
"location %s for %pOF\n", loc, child);
continue;
}
cpu = 2 * chip + core;
if (sat->nr < 0)
sat->nr = chip;
else if (sat->nr != chip) {
printk(KERN_ERR "wf_sat_create: can't cope with "
"multiple CPU chips on one SAT (%s)\n", loc);
continue;
}
if (of_node_is_type(child, "voltage-sensor")) {
name = "cpu-voltage";
shift = 4;
vsens[core] = index;
} else if (of_node_is_type(child, "current-sensor")) {
name = "cpu-current";
shift = 8;
isens[core] = index;
} else if (of_node_is_type(child, "temp-sensor")) {
name = "cpu-temp";
shift = 10;
} else
continue; /* hmmm shouldn't happen */
/* the +16 is enough for "cpu-voltage-n" */
sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL);
if (sens == NULL) {
printk(KERN_ERR "wf_sat_create: couldn't create "
"%s sensor %d (no memory)\n", name, cpu);
continue;
}
sens->index = index;
sens->index2 = -1;
sens->shift = shift;
sens->sat = sat;
sens->sens.ops = &wf_sat_ops;
sens->sens.name = (char *) (sens + 1);
snprintf((char *)sens->sens.name, 16, "%s-%d", name, cpu);
if (wf_register_sensor(&sens->sens))
kfree(sens);
else {
list_add(&sens->link, &sat->sensors);
kref_get(&sat->ref);
}
}
/* make the power sensors */
for (core = 0; core < 2; ++core) {
if (vsens[core] < 0 || isens[core] < 0)
continue;
cpu = 2 * sat->nr + core;
sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL);
if (sens == NULL) {
printk(KERN_ERR "wf_sat_create: couldn't create power "
"sensor %d (no memory)\n", cpu);
continue;
}
sens->index = vsens[core];
sens->index2 = isens[core];
sens->shift = 0;
sens->sat = sat;
sens->sens.ops = &wf_sat_ops;
sens->sens.name = (char *) (sens + 1);
snprintf((char *)sens->sens.name, 16, "cpu-power-%d", cpu);
if (wf_register_sensor(&sens->sens))
kfree(sens);
else {
list_add(&sens->link, &sat->sensors);
kref_get(&sat->ref);
}
}
if (sat->nr >= 0)
sats[sat->nr] = sat;
return 0;
}
i2c: Make remove callback return void The value returned by an i2c driver's remove function is mostly ignored. (Only an error message is printed if the value is non-zero that the error is ignored.) So change the prototype of the remove function to return no value. This way driver authors are not tempted to assume that passing an error to the upper layer is a good idea. All drivers are adapted accordingly. There is no intended change of behaviour, all callbacks were prepared to return 0 before. Reviewed-by: Peter Senna Tschudin <peter.senna@gmail.com> Reviewed-by: Jeremy Kerr <jk@codeconstruct.com.au> Reviewed-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com> Reviewed-by: Javier Martinez Canillas <javierm@redhat.com> Reviewed-by: Crt Mori <cmo@melexis.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Acked-by: Marek Behún <kabel@kernel.org> # for leds-turris-omnia Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Reviewed-by: Petr Machata <petrm@nvidia.com> # for mlxsw Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com> # for surface3_power Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> # for bmc150-accel-i2c + kxcjk-1013 Reviewed-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> # for media/* + staging/media/* Acked-by: Miguel Ojeda <ojeda@kernel.org> # for auxdisplay/ht16k33 + auxdisplay/lcd2s Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> # for versaclock5 Reviewed-by: Ajay Gupta <ajayg@nvidia.com> # for ucsi_ccg Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> # for iio Acked-by: Peter Rosin <peda@axentia.se> # for i2c-mux-*, max9860 Acked-by: Adrien Grassein <adrien.grassein@gmail.com> # for lontium-lt8912b Reviewed-by: Jean Delvare <jdelvare@suse.de> # for hwmon, i2c-core and i2c/muxes Acked-by: Corey Minyard <cminyard@mvista.com> # for IPMI Reviewed-by: Vladimir Oltean <olteanv@gmail.com> Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com> Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com> # for drivers/power Acked-by: Krzysztof Hałasa <khalasa@piap.pl> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: Wolfram Sang <wsa@kernel.org>
2022-08-15 11:02:30 +03:00
static void wf_sat_remove(struct i2c_client *client)
{
struct wf_sat *sat = i2c_get_clientdata(client);
struct wf_sat_sensor *sens;
/* release sensors */
while(!list_empty(&sat->sensors)) {
sens = list_first_entry(&sat->sensors,
struct wf_sat_sensor, link);
list_del(&sens->link);
wf_unregister_sensor(&sens->sens);
}
sat->i2c = NULL;
kref_put(&sat->ref, wf_sat_release);
}
static const struct i2c_device_id wf_sat_id[] = {
{ "MAC,smu-sat", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, wf_sat_id);
static const struct of_device_id wf_sat_of_id[] = {
{ .compatible = "smu-sat", },
{ }
};
MODULE_DEVICE_TABLE(of, wf_sat_of_id);
static struct i2c_driver wf_sat_driver = {
.driver = {
.name = "wf_smu_sat",
.of_match_table = wf_sat_of_id,
},
.probe = wf_sat_probe,
.remove = wf_sat_remove,
.id_table = wf_sat_id,
};
module_i2c_driver(wf_sat_driver);
MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
MODULE_DESCRIPTION("SMU satellite sensors for PowerMac thermal control");
MODULE_LICENSE("GPL");