diff options
Diffstat (limited to 'drivers/hwmon/max127.c')
-rw-r--r-- | drivers/hwmon/max127.c | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/drivers/hwmon/max127.c b/drivers/hwmon/max127.c new file mode 100644 index 000000000000..402ffdc2f425 --- /dev/null +++ b/drivers/hwmon/max127.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for MAX127. + * + * Copyright (c) 2020 Facebook Inc. + */ + +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> + +/* + * MAX127 Control Byte. Refer to MAX127 datasheet, Table 1 "Control-Byte + * Format" for details. + */ +#define MAX127_CTRL_START BIT(7) +#define MAX127_CTRL_SEL_SHIFT 4 +#define MAX127_CTRL_RNG BIT(3) +#define MAX127_CTRL_BIP BIT(2) +#define MAX127_CTRL_PD1 BIT(1) +#define MAX127_CTRL_PD0 BIT(0) + +#define MAX127_NUM_CHANNELS 8 +#define MAX127_SET_CHANNEL(ch) (((ch) & 7) << MAX127_CTRL_SEL_SHIFT) + +/* + * MAX127 channel input ranges. Refer to MAX127 datasheet, Table 3 "Range + * and Polarity Selection" for details. + */ +#define MAX127_FULL_RANGE 10000 /* 10V */ +#define MAX127_HALF_RANGE 5000 /* 5V */ + +/* + * MAX127 returns 2 bytes at read: + * - the first byte contains data[11:4]. + * - the second byte contains data[3:0] (MSB) and 4 dummy 0s (LSB). + * Refer to MAX127 datasheet, "Read a Conversion (Read Cycle)" section + * for details. + */ +#define MAX127_DATA_LEN 2 +#define MAX127_DATA_SHIFT 4 + +#define MAX127_SIGN_BIT BIT(11) + +struct max127_data { + struct mutex lock; + struct i2c_client *client; + u8 ctrl_byte[MAX127_NUM_CHANNELS]; +}; + +static int max127_select_channel(struct i2c_client *client, u8 ctrl_byte) +{ + int status; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = sizeof(ctrl_byte), + .buf = &ctrl_byte, + }; + + status = i2c_transfer(client->adapter, &msg, 1); + if (status < 0) + return status; + if (status != 1) + return -EIO; + + return 0; +} + +static int max127_read_channel(struct i2c_client *client, long *val) +{ + int status; + u8 i2c_data[MAX127_DATA_LEN]; + struct i2c_msg msg = { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(i2c_data), + .buf = i2c_data, + }; + + status = i2c_transfer(client->adapter, &msg, 1); + if (status < 0) + return status; + if (status != 1) + return -EIO; + + *val = (i2c_data[1] >> MAX127_DATA_SHIFT) | + ((u16)i2c_data[0] << MAX127_DATA_SHIFT); + return 0; +} + +static long max127_process_raw(u8 ctrl_byte, long raw) +{ + long scale, weight; + + /* + * MAX127's data coding is binary in unipolar mode with 1 LSB = + * (Full-Scale/4096) and two’s complement binary in bipolar mode + * with 1 LSB = [(2 x |FS|)/4096]. + * Refer to MAX127 datasheet, "Transfer Function" section for + * details. + */ + scale = (ctrl_byte & MAX127_CTRL_RNG) ? MAX127_FULL_RANGE : + MAX127_HALF_RANGE; + if (ctrl_byte & MAX127_CTRL_BIP) { + weight = (raw & MAX127_SIGN_BIT); + raw &= ~MAX127_SIGN_BIT; + raw -= weight; + raw *= 2; + } + + return raw * scale / 4096; +} + +static int max127_read_input(struct max127_data *data, int channel, long *val) +{ + long raw; + int status; + struct i2c_client *client = data->client; + u8 ctrl_byte = data->ctrl_byte[channel]; + + mutex_lock(&data->lock); + + status = max127_select_channel(client, ctrl_byte); + if (status) + goto exit; + + status = max127_read_channel(client, &raw); + if (status) + goto exit; + + *val = max127_process_raw(ctrl_byte, raw); + +exit: + mutex_unlock(&data->lock); + return status; +} + +static int max127_read_min(struct max127_data *data, int channel, long *val) +{ + u8 rng_bip = (data->ctrl_byte[channel] >> 2) & 3; + static const int min_input_map[4] = { + 0, /* RNG=0, BIP=0 */ + -MAX127_HALF_RANGE, /* RNG=0, BIP=1 */ + 0, /* RNG=1, BIP=0 */ + -MAX127_FULL_RANGE, /* RNG=1, BIP=1 */ + }; + + *val = min_input_map[rng_bip]; + return 0; +} + +static int max127_read_max(struct max127_data *data, int channel, long *val) +{ + u8 rng_bip = (data->ctrl_byte[channel] >> 2) & 3; + static const int max_input_map[4] = { + MAX127_HALF_RANGE, /* RNG=0, BIP=0 */ + MAX127_HALF_RANGE, /* RNG=0, BIP=1 */ + MAX127_FULL_RANGE, /* RNG=1, BIP=0 */ + MAX127_FULL_RANGE, /* RNG=1, BIP=1 */ + }; + + *val = max_input_map[rng_bip]; + return 0; +} + +static int max127_write_min(struct max127_data *data, int channel, long val) +{ + u8 ctrl; + + mutex_lock(&data->lock); + + ctrl = data->ctrl_byte[channel]; + if (val <= -MAX127_FULL_RANGE) { + ctrl |= (MAX127_CTRL_RNG | MAX127_CTRL_BIP); + } else if (val < 0) { + ctrl |= MAX127_CTRL_BIP; + ctrl &= ~MAX127_CTRL_RNG; + } else { + ctrl &= ~MAX127_CTRL_BIP; + } + data->ctrl_byte[channel] = ctrl; + + mutex_unlock(&data->lock); + + return 0; +} + +static int max127_write_max(struct max127_data *data, int channel, long val) +{ + mutex_lock(&data->lock); + + if (val >= MAX127_FULL_RANGE) + data->ctrl_byte[channel] |= MAX127_CTRL_RNG; + else + data->ctrl_byte[channel] &= ~MAX127_CTRL_RNG; + + mutex_unlock(&data->lock); + + return 0; +} + +static umode_t max127_is_visible(const void *_data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_in) { + switch (attr) { + case hwmon_in_input: + return 0444; + + case hwmon_in_min: + case hwmon_in_max: + return 0644; + + default: + break; + } + } + + return 0; +} + +static int max127_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int status; + struct max127_data *data = dev_get_drvdata(dev); + + if (type != hwmon_in) + return -EOPNOTSUPP; + + switch (attr) { + case hwmon_in_input: + status = max127_read_input(data, channel, val); + break; + + case hwmon_in_min: + status = max127_read_min(data, channel, val); + break; + + case hwmon_in_max: + status = max127_read_max(data, channel, val); + break; + + default: + status = -EOPNOTSUPP; + break; + } + + return status; +} + +static int max127_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int status; + struct max127_data *data = dev_get_drvdata(dev); + + if (type != hwmon_in) + return -EOPNOTSUPP; + + switch (attr) { + case hwmon_in_min: + status = max127_write_min(data, channel, val); + break; + + case hwmon_in_max: + status = max127_write_max(data, channel, val); + break; + + default: + status = -EOPNOTSUPP; + break; + } + + return status; +} + +static const struct hwmon_ops max127_hwmon_ops = { + .is_visible = max127_is_visible, + .read = max127_read, + .write = max127_write, +}; + +static const struct hwmon_channel_info *max127_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX), + NULL, +}; + +static const struct hwmon_chip_info max127_chip_info = { + .ops = &max127_hwmon_ops, + .info = max127_info, +}; + +static int max127_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i; + struct device *hwmon_dev; + struct max127_data *data; + struct device *dev = &client->dev; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->lock); + for (i = 0; i < ARRAY_SIZE(data->ctrl_byte); i++) + data->ctrl_byte[i] = (MAX127_CTRL_START | + MAX127_SET_CHANNEL(i)); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &max127_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max127_id[] = { + { "max127", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max127_id); + +static struct i2c_driver max127_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max127", + }, + .probe = max127_probe, + .id_table = max127_id, +}; + +module_i2c_driver(max127_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mike Choi <mikechoi@fb.com>"); +MODULE_AUTHOR("Tao Ren <rentao.bupt@gmail.com>"); +MODULE_DESCRIPTION("MAX127 Hardware Monitoring driver"); |