Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Nuno Sá | 5089 | 100.00% | 1 | 100.00% |
Total | 5089 | 1 |
// SPDX-License-Identifier: GPL-2.0 /* * Analog Devices LTC2947 high precision power and energy monitor * * Copyright 2019 Analog Devices Inc. */ #include <linux/bitfield.h> #include <linux/bits.h> #include <linux/clk.h> #include <linux/device.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/module.h> #include <linux/of.h> #include <linux/regmap.h> #include "ltc2947.h" /* register's */ #define LTC2947_REG_PAGE_CTRL 0xFF #define LTC2947_REG_CTRL 0xF0 #define LTC2947_REG_TBCTL 0xE9 #define LTC2947_CONT_MODE_MASK BIT(3) #define LTC2947_CONT_MODE(x) FIELD_PREP(LTC2947_CONT_MODE_MASK, x) #define LTC2947_PRE_MASK GENMASK(2, 0) #define LTC2947_PRE(x) FIELD_PREP(LTC2947_PRE_MASK, x) #define LTC2947_DIV_MASK GENMASK(7, 3) #define LTC2947_DIV(x) FIELD_PREP(LTC2947_DIV_MASK, x) #define LTC2947_SHUTDOWN_MASK BIT(0) #define LTC2947_REG_ACCUM_POL 0xE1 #define LTC2947_ACCUM_POL_1_MASK GENMASK(1, 0) #define LTC2947_ACCUM_POL_1(x) FIELD_PREP(LTC2947_ACCUM_POL_1_MASK, x) #define LTC2947_ACCUM_POL_2_MASK GENMASK(3, 2) #define LTC2947_ACCUM_POL_2(x) FIELD_PREP(LTC2947_ACCUM_POL_2_MASK, x) #define LTC2947_REG_ACCUM_DEADBAND 0xE4 #define LTC2947_REG_GPIOSTATCTL 0x67 #define LTC2947_GPIO_EN_MASK BIT(0) #define LTC2947_GPIO_EN(x) FIELD_PREP(LTC2947_GPIO_EN_MASK, x) #define LTC2947_GPIO_FAN_EN_MASK BIT(6) #define LTC2947_GPIO_FAN_EN(x) FIELD_PREP(LTC2947_GPIO_FAN_EN_MASK, x) #define LTC2947_GPIO_FAN_POL_MASK BIT(7) #define LTC2947_GPIO_FAN_POL(x) FIELD_PREP(LTC2947_GPIO_FAN_POL_MASK, x) #define LTC2947_REG_GPIO_ACCUM 0xE3 /* 200Khz */ #define LTC2947_CLK_MIN 200000 /* 25Mhz */ #define LTC2947_CLK_MAX 25000000 #define LTC2947_PAGE0 0 #define LTC2947_PAGE1 1 /* Voltage registers */ #define LTC2947_REG_VOLTAGE 0xA0 #define LTC2947_REG_VOLTAGE_MAX 0x50 #define LTC2947_REG_VOLTAGE_MIN 0x52 #define LTC2947_REG_VOLTAGE_THRE_H 0x90 #define LTC2947_REG_VOLTAGE_THRE_L 0x92 #define LTC2947_REG_DVCC 0xA4 #define LTC2947_REG_DVCC_MAX 0x58 #define LTC2947_REG_DVCC_MIN 0x5A #define LTC2947_REG_DVCC_THRE_H 0x98 #define LTC2947_REG_DVCC_THRE_L 0x9A #define LTC2947_VOLTAGE_GEN_CHAN 0 #define LTC2947_VOLTAGE_DVCC_CHAN 1 /* in mV */ #define VOLTAGE_MAX 15500 #define VOLTAGE_MIN -300 #define VDVCC_MAX 15000 #define VDVCC_MIN 4750 /* Current registers */ #define LTC2947_REG_CURRENT 0x90 #define LTC2947_REG_CURRENT_MAX 0x40 #define LTC2947_REG_CURRENT_MIN 0x42 #define LTC2947_REG_CURRENT_THRE_H 0x80 #define LTC2947_REG_CURRENT_THRE_L 0x82 /* in mA */ #define CURRENT_MAX 30000 #define CURRENT_MIN -30000 /* Power registers */ #define LTC2947_REG_POWER 0x93 #define LTC2947_REG_POWER_MAX 0x44 #define LTC2947_REG_POWER_MIN 0x46 #define LTC2947_REG_POWER_THRE_H 0x84 #define LTC2947_REG_POWER_THRE_L 0x86 /* in uW */ #define POWER_MAX 450000000 #define POWER_MIN -450000000 /* Temperature registers */ #define LTC2947_REG_TEMP 0xA2 #define LTC2947_REG_TEMP_MAX 0x54 #define LTC2947_REG_TEMP_MIN 0x56 #define LTC2947_REG_TEMP_THRE_H 0x94 #define LTC2947_REG_TEMP_THRE_L 0x96 #define LTC2947_REG_TEMP_FAN_THRE_H 0x9C #define LTC2947_REG_TEMP_FAN_THRE_L 0x9E #define LTC2947_TEMP_FAN_CHAN 1 /* in millidegress Celsius */ #define TEMP_MAX 85000 #define TEMP_MIN -40000 /* Energy registers */ #define LTC2947_REG_ENERGY1 0x06 #define LTC2947_REG_ENERGY2 0x16 /* Status/Alarm/Overflow registers */ #define LTC2947_REG_STATUS 0x80 #define LTC2947_REG_STATVT 0x81 #define LTC2947_REG_STATIP 0x82 #define LTC2947_REG_STATVDVCC 0x87 #define LTC2947_ALERTS_SIZE (LTC2947_REG_STATVDVCC - LTC2947_REG_STATUS) #define LTC2947_MAX_VOLTAGE_MASK BIT(0) #define LTC2947_MIN_VOLTAGE_MASK BIT(1) #define LTC2947_MAX_CURRENT_MASK BIT(0) #define LTC2947_MIN_CURRENT_MASK BIT(1) #define LTC2947_MAX_POWER_MASK BIT(2) #define LTC2947_MIN_POWER_MASK BIT(3) #define LTC2947_MAX_TEMP_MASK BIT(2) #define LTC2947_MIN_TEMP_MASK BIT(3) #define LTC2947_MAX_TEMP_FAN_MASK BIT(4) #define LTC2947_MIN_TEMP_FAN_MASK BIT(5) struct ltc2947_data { struct regmap *map; struct device *dev; /* * The mutex is needed because the device has 2 memory pages. When * reading/writing the correct page needs to be set so that, the * complete sequence select_page->read/write needs to be protected. */ struct mutex lock; u32 lsb_energy; bool gpio_out; }; static int __ltc2947_val_read16(const struct ltc2947_data *st, const u8 reg, u64 *val) { __be16 __val = 0; int ret; ret = regmap_bulk_read(st->map, reg, &__val, 2); if (ret) return ret; *val = be16_to_cpu(__val); return 0; } static int __ltc2947_val_read24(const struct ltc2947_data *st, const u8 reg, u64 *val) { __be32 __val = 0; int ret; ret = regmap_bulk_read(st->map, reg, &__val, 3); if (ret) return ret; *val = be32_to_cpu(__val) >> 8; return 0; } static int __ltc2947_val_read64(const struct ltc2947_data *st, const u8 reg, u64 *val) { __be64 __val = 0; int ret; ret = regmap_bulk_read(st->map, reg, &__val, 6); if (ret) return ret; *val = be64_to_cpu(__val) >> 16; return 0; } static int ltc2947_val_read(struct ltc2947_data *st, const u8 reg, const u8 page, const size_t size, s64 *val) { int ret; u64 __val = 0; mutex_lock(&st->lock); ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, page); if (ret) { mutex_unlock(&st->lock); return ret; } dev_dbg(st->dev, "Read val, reg:%02X, p:%d sz:%zu\n", reg, page, size); switch (size) { case 2: ret = __ltc2947_val_read16(st, reg, &__val); break; case 3: ret = __ltc2947_val_read24(st, reg, &__val); break; case 6: ret = __ltc2947_val_read64(st, reg, &__val); break; default: ret = -EINVAL; break; } mutex_unlock(&st->lock); if (ret) return ret; *val = sign_extend64(__val, (8 * size) - 1); dev_dbg(st->dev, "Got s:%lld, u:%016llX\n", *val, __val); return 0; } static int __ltc2947_val_write64(const struct ltc2947_data *st, const u8 reg, const u64 val) { __be64 __val; __val = cpu_to_be64(val << 16); return regmap_bulk_write(st->map, reg, &__val, 6); } static int __ltc2947_val_write16(const struct ltc2947_data *st, const u8 reg, const u16 val) { __be16 __val; __val = cpu_to_be16(val); return regmap_bulk_write(st->map, reg, &__val, 2); } static int ltc2947_val_write(struct ltc2947_data *st, const u8 reg, const u8 page, const size_t size, const u64 val) { int ret; mutex_lock(&st->lock); /* set device on correct page */ ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, page); if (ret) { mutex_unlock(&st->lock); return ret; } dev_dbg(st->dev, "Write val, r:%02X, p:%d, sz:%zu, val:%016llX\n", reg, page, size, val); switch (size) { case 2: ret = __ltc2947_val_write16(st, reg, val); break; case 6: ret = __ltc2947_val_write64(st, reg, val); break; default: ret = -EINVAL; break; } mutex_unlock(&st->lock); return ret; } static int ltc2947_reset_history(struct ltc2947_data *st, const u8 reg_h, const u8 reg_l) { int ret; /* * let's reset the tracking register's. Tracking register's have all * 2 bytes size */ ret = ltc2947_val_write(st, reg_h, LTC2947_PAGE0, 2, 0x8000U); if (ret) return ret; return ltc2947_val_write(st, reg_l, LTC2947_PAGE0, 2, 0x7FFFU); } static int ltc2947_alarm_read(struct ltc2947_data *st, const u8 reg, const u32 mask, long *val) { u8 offset = reg - LTC2947_REG_STATUS; /* +1 to include status reg */ char alarms[LTC2947_ALERTS_SIZE + 1]; int ret = 0; memset(alarms, 0, sizeof(alarms)); mutex_lock(&st->lock); ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, LTC2947_PAGE0); if (ret) goto unlock; dev_dbg(st->dev, "Read alarm, reg:%02X, mask:%02X\n", reg, mask); /* * As stated in the datasheet, when Threshold and Overflow registers * are used, the status and all alert registers must be read in one * multi-byte transaction. */ ret = regmap_bulk_read(st->map, LTC2947_REG_STATUS, alarms, sizeof(alarms)); if (ret) goto unlock; /* get the alarm */ *val = !!(alarms[offset] & mask); unlock: mutex_unlock(&st->lock); return ret; } static ssize_t ltc2947_show_value(struct device *dev, struct device_attribute *da, char *buf) { struct ltc2947_data *st = dev_get_drvdata(dev); struct sensor_device_attribute *attr = to_sensor_dev_attr(da); int ret; s64 val = 0; ret = ltc2947_val_read(st, attr->index, LTC2947_PAGE0, 6, &val); if (ret) return ret; /* value in microJoule. st->lsb_energy was multiplied by 10E9 */ val = div_s64(val * st->lsb_energy, 1000); return sprintf(buf, "%lld\n", val); } static int ltc2947_read_temp(struct device *dev, const u32 attr, long *val, const int channel) { int ret; struct ltc2947_data *st = dev_get_drvdata(dev); s64 __val = 0; switch (attr) { case hwmon_temp_input: ret = ltc2947_val_read(st, LTC2947_REG_TEMP, LTC2947_PAGE0, 2, &__val); break; case hwmon_temp_highest: ret = ltc2947_val_read(st, LTC2947_REG_TEMP_MAX, LTC2947_PAGE0, 2, &__val); break; case hwmon_temp_lowest: ret = ltc2947_val_read(st, LTC2947_REG_TEMP_MIN, LTC2947_PAGE0, 2, &__val); break; case hwmon_temp_max_alarm: if (channel == LTC2947_TEMP_FAN_CHAN) return ltc2947_alarm_read(st, LTC2947_REG_STATVT, LTC2947_MAX_TEMP_FAN_MASK, val); return ltc2947_alarm_read(st, LTC2947_REG_STATVT, LTC2947_MAX_TEMP_MASK, val); case hwmon_temp_min_alarm: if (channel == LTC2947_TEMP_FAN_CHAN) return ltc2947_alarm_read(st, LTC2947_REG_STATVT, LTC2947_MIN_TEMP_FAN_MASK, val); return ltc2947_alarm_read(st, LTC2947_REG_STATVT, LTC2947_MIN_TEMP_MASK, val); case hwmon_temp_max: if (channel == LTC2947_TEMP_FAN_CHAN) ret = ltc2947_val_read(st, LTC2947_REG_TEMP_FAN_THRE_H, LTC2947_PAGE1, 2, &__val); else ret = ltc2947_val_read(st, LTC2947_REG_TEMP_THRE_H, LTC2947_PAGE1, 2, &__val); break; case hwmon_temp_min: if (channel == LTC2947_TEMP_FAN_CHAN) ret = ltc2947_val_read(st, LTC2947_REG_TEMP_FAN_THRE_L, LTC2947_PAGE1, 2, &__val); else ret = ltc2947_val_read(st, LTC2947_REG_TEMP_THRE_L, LTC2947_PAGE1, 2, &__val); break; default: return -ENOTSUPP; } if (ret) return ret; /* in milidegrees celcius, temp is given by: */ *val = (__val * 204) + 550; return 0; } static int ltc2947_read_power(struct device *dev, const u32 attr, long *val) { struct ltc2947_data *st = dev_get_drvdata(dev); int ret; u32 lsb = 200000; /* in uW */ s64 __val = 0; switch (attr) { case hwmon_power_input: ret = ltc2947_val_read(st, LTC2947_REG_POWER, LTC2947_PAGE0, 3, &__val); lsb = 50000; break; case hwmon_power_input_highest: ret = ltc2947_val_read(st, LTC2947_REG_POWER_MAX, LTC2947_PAGE0, 2, &__val); break; case hwmon_power_input_lowest: ret = ltc2947_val_read(st, LTC2947_REG_POWER_MIN, LTC2947_PAGE0, 2, &__val); break; case hwmon_power_max_alarm: return ltc2947_alarm_read(st, LTC2947_REG_STATIP, LTC2947_MAX_POWER_MASK, val); case hwmon_power_min_alarm: return ltc2947_alarm_read(st, LTC2947_REG_STATIP, LTC2947_MIN_POWER_MASK, val); case hwmon_power_max: ret = ltc2947_val_read(st, LTC2947_REG_POWER_THRE_H, LTC2947_PAGE1, 2, &__val); break; case hwmon_power_min: ret = ltc2947_val_read(st, LTC2947_REG_POWER_THRE_L, LTC2947_PAGE1, 2, &__val); break; default: return -ENOTSUPP; } if (ret) return ret; *val = __val * lsb; return 0; } static int ltc2947_read_curr(struct device *dev, const u32 attr, long *val) { struct ltc2947_data *st = dev_get_drvdata(dev); int ret; u8 lsb = 12; /* in mA */ s64 __val = 0; switch (attr) { case hwmon_curr_input: ret = ltc2947_val_read(st, LTC2947_REG_CURRENT, LTC2947_PAGE0, 3, &__val); lsb = 3; break; case hwmon_curr_highest: ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_MAX, LTC2947_PAGE0, 2, &__val); break; case hwmon_curr_lowest: ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_MIN, LTC2947_PAGE0, 2, &__val); break; case hwmon_curr_max_alarm: return ltc2947_alarm_read(st, LTC2947_REG_STATIP, LTC2947_MAX_CURRENT_MASK, val); case hwmon_curr_min_alarm: return ltc2947_alarm_read(st, LTC2947_REG_STATIP, LTC2947_MIN_CURRENT_MASK, val); case hwmon_curr_max: ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_THRE_H, LTC2947_PAGE1, 2, &__val); break; case hwmon_curr_min: ret = ltc2947_val_read(st, LTC2947_REG_CURRENT_THRE_L, LTC2947_PAGE1, 2, &__val); break; default: return -ENOTSUPP; } if (ret) return ret; *val = __val * lsb; return 0; } static int ltc2947_read_in(struct device *dev, const u32 attr, long *val, const int channel) { struct ltc2947_data *st = dev_get_drvdata(dev); int ret; u8 lsb = 2; /* in mV */ s64 __val = 0; if (channel < 0 || channel > LTC2947_VOLTAGE_DVCC_CHAN) { dev_err(st->dev, "Invalid chan%d for voltage", channel); return -EINVAL; } switch (attr) { case hwmon_in_input: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) { ret = ltc2947_val_read(st, LTC2947_REG_DVCC, LTC2947_PAGE0, 2, &__val); lsb = 145; } else { ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE, LTC2947_PAGE0, 2, &__val); } break; case hwmon_in_highest: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) { ret = ltc2947_val_read(st, LTC2947_REG_DVCC_MAX, LTC2947_PAGE0, 2, &__val); lsb = 145; } else { ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_MAX, LTC2947_PAGE0, 2, &__val); } break; case hwmon_in_lowest: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) { ret = ltc2947_val_read(st, LTC2947_REG_DVCC_MIN, LTC2947_PAGE0, 2, &__val); lsb = 145; } else { ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_MIN, LTC2947_PAGE0, 2, &__val); } break; case hwmon_in_max_alarm: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) return ltc2947_alarm_read(st, LTC2947_REG_STATVDVCC, LTC2947_MAX_VOLTAGE_MASK, val); return ltc2947_alarm_read(st, LTC2947_REG_STATVT, LTC2947_MAX_VOLTAGE_MASK, val); case hwmon_in_min_alarm: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) return ltc2947_alarm_read(st, LTC2947_REG_STATVDVCC, LTC2947_MIN_VOLTAGE_MASK, val); return ltc2947_alarm_read(st, LTC2947_REG_STATVT, LTC2947_MIN_VOLTAGE_MASK, val); case hwmon_in_max: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) { ret = ltc2947_val_read(st, LTC2947_REG_DVCC_THRE_H, LTC2947_PAGE1, 2, &__val); lsb = 145; } else { ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_THRE_H, LTC2947_PAGE1, 2, &__val); } break; case hwmon_in_min: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) { ret = ltc2947_val_read(st, LTC2947_REG_DVCC_THRE_L, LTC2947_PAGE1, 2, &__val); lsb = 145; } else { ret = ltc2947_val_read(st, LTC2947_REG_VOLTAGE_THRE_L, LTC2947_PAGE1, 2, &__val); } break; default: return -ENOTSUPP; } if (ret) return ret; *val = __val * lsb; return 0; } static int ltc2947_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { switch (type) { case hwmon_in: return ltc2947_read_in(dev, attr, val, channel); case hwmon_curr: return ltc2947_read_curr(dev, attr, val); case hwmon_power: return ltc2947_read_power(dev, attr, val); case hwmon_temp: return ltc2947_read_temp(dev, attr, val, channel); default: return -ENOTSUPP; } } static int ltc2947_write_temp(struct device *dev, const u32 attr, long val, const int channel) { struct ltc2947_data *st = dev_get_drvdata(dev); if (channel < 0 || channel > LTC2947_TEMP_FAN_CHAN) { dev_err(st->dev, "Invalid chan%d for temperature", channel); return -EINVAL; } switch (attr) { case hwmon_temp_reset_history: if (val != 1) return -EINVAL; return ltc2947_reset_history(st, LTC2947_REG_TEMP_MAX, LTC2947_REG_TEMP_MIN); case hwmon_temp_max: val = clamp_val(val, TEMP_MIN, TEMP_MAX); if (channel == LTC2947_TEMP_FAN_CHAN) { if (!st->gpio_out) return -ENOTSUPP; return ltc2947_val_write(st, LTC2947_REG_TEMP_FAN_THRE_H, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val - 550, 204)); } return ltc2947_val_write(st, LTC2947_REG_TEMP_THRE_H, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val - 550, 204)); case hwmon_temp_min: val = clamp_val(val, TEMP_MIN, TEMP_MAX); if (channel == LTC2947_TEMP_FAN_CHAN) { if (!st->gpio_out) return -ENOTSUPP; return ltc2947_val_write(st, LTC2947_REG_TEMP_FAN_THRE_L, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val - 550, 204)); } return ltc2947_val_write(st, LTC2947_REG_TEMP_THRE_L, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val - 550, 204)); default: return -ENOTSUPP; } } static int ltc2947_write_power(struct device *dev, const u32 attr, long val) { struct ltc2947_data *st = dev_get_drvdata(dev); switch (attr) { case hwmon_power_reset_history: if (val != 1) return -EINVAL; return ltc2947_reset_history(st, LTC2947_REG_POWER_MAX, LTC2947_REG_POWER_MIN); case hwmon_power_max: val = clamp_val(val, POWER_MIN, POWER_MAX); return ltc2947_val_write(st, LTC2947_REG_POWER_THRE_H, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val, 200000)); case hwmon_power_min: val = clamp_val(val, POWER_MIN, POWER_MAX); return ltc2947_val_write(st, LTC2947_REG_POWER_THRE_L, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val, 200000)); default: return -ENOTSUPP; } } static int ltc2947_write_curr(struct device *dev, const u32 attr, long val) { struct ltc2947_data *st = dev_get_drvdata(dev); switch (attr) { case hwmon_curr_reset_history: if (val != 1) return -EINVAL; return ltc2947_reset_history(st, LTC2947_REG_CURRENT_MAX, LTC2947_REG_CURRENT_MIN); case hwmon_curr_max: val = clamp_val(val, CURRENT_MIN, CURRENT_MAX); return ltc2947_val_write(st, LTC2947_REG_CURRENT_THRE_H, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val, 12)); case hwmon_curr_min: val = clamp_val(val, CURRENT_MIN, CURRENT_MAX); return ltc2947_val_write(st, LTC2947_REG_CURRENT_THRE_L, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val, 12)); default: return -ENOTSUPP; } } static int ltc2947_write_in(struct device *dev, const u32 attr, long val, const int channel) { struct ltc2947_data *st = dev_get_drvdata(dev); if (channel > LTC2947_VOLTAGE_DVCC_CHAN) { dev_err(st->dev, "Invalid chan%d for voltage", channel); return -EINVAL; } switch (attr) { case hwmon_in_reset_history: if (val != 1) return -EINVAL; if (channel == LTC2947_VOLTAGE_DVCC_CHAN) return ltc2947_reset_history(st, LTC2947_REG_DVCC_MAX, LTC2947_REG_DVCC_MIN); return ltc2947_reset_history(st, LTC2947_REG_VOLTAGE_MAX, LTC2947_REG_VOLTAGE_MIN); case hwmon_in_max: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) { val = clamp_val(val, VDVCC_MIN, VDVCC_MAX); return ltc2947_val_write(st, LTC2947_REG_DVCC_THRE_H, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val, 145)); } val = clamp_val(val, VOLTAGE_MIN, VOLTAGE_MAX); return ltc2947_val_write(st, LTC2947_REG_VOLTAGE_THRE_H, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val, 2)); case hwmon_in_min: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) { val = clamp_val(val, VDVCC_MIN, VDVCC_MAX); return ltc2947_val_write(st, LTC2947_REG_DVCC_THRE_L, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val, 145)); } val = clamp_val(val, VOLTAGE_MIN, VOLTAGE_MAX); return ltc2947_val_write(st, LTC2947_REG_VOLTAGE_THRE_L, LTC2947_PAGE1, 2, DIV_ROUND_CLOSEST(val, 2)); default: return -ENOTSUPP; } } static int ltc2947_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { switch (type) { case hwmon_in: return ltc2947_write_in(dev, attr, val, channel); case hwmon_curr: return ltc2947_write_curr(dev, attr, val); case hwmon_power: return ltc2947_write_power(dev, attr, val); case hwmon_temp: return ltc2947_write_temp(dev, attr, val, channel); default: return -ENOTSUPP; } } static int ltc2947_read_labels(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, const char **str) { switch (type) { case hwmon_in: if (channel == LTC2947_VOLTAGE_DVCC_CHAN) *str = "DVCC"; else *str = "VP-VM"; return 0; case hwmon_curr: *str = "IP-IM"; return 0; case hwmon_temp: if (channel == LTC2947_TEMP_FAN_CHAN) *str = "TEMPFAN"; else *str = "Ambient"; return 0; case hwmon_power: *str = "Power"; return 0; default: return -ENOTSUPP; } } static int ltc2947_in_is_visible(const u32 attr) { switch (attr) { case hwmon_in_input: case hwmon_in_highest: case hwmon_in_lowest: case hwmon_in_max_alarm: case hwmon_in_min_alarm: case hwmon_in_label: return 0444; case hwmon_in_reset_history: return 0200; case hwmon_in_max: case hwmon_in_min: return 0644; default: return 0; } } static int ltc2947_curr_is_visible(const u32 attr) { switch (attr) { case hwmon_curr_input: case hwmon_curr_highest: case hwmon_curr_lowest: case hwmon_curr_max_alarm: case hwmon_curr_min_alarm: case hwmon_curr_label: return 0444; case hwmon_curr_reset_history: return 0200; case hwmon_curr_max: case hwmon_curr_min: return 0644; default: return 0; } } static int ltc2947_power_is_visible(const u32 attr) { switch (attr) { case hwmon_power_input: case hwmon_power_input_highest: case hwmon_power_input_lowest: case hwmon_power_label: case hwmon_power_max_alarm: case hwmon_power_min_alarm: return 0444; case hwmon_power_reset_history: return 0200; case hwmon_power_max: case hwmon_power_min: return 0644; default: return 0; } } static int ltc2947_temp_is_visible(const u32 attr) { switch (attr) { case hwmon_temp_input: case hwmon_temp_highest: case hwmon_temp_lowest: case hwmon_temp_max_alarm: case hwmon_temp_min_alarm: case hwmon_temp_label: return 0444; case hwmon_temp_reset_history: return 0200; case hwmon_temp_max: case hwmon_temp_min: return 0644; default: return 0; } } static umode_t ltc2947_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { switch (type) { case hwmon_in: return ltc2947_in_is_visible(attr); case hwmon_curr: return ltc2947_curr_is_visible(attr); case hwmon_power: return ltc2947_power_is_visible(attr); case hwmon_temp: return ltc2947_temp_is_visible(attr); default: return 0; } } static const struct hwmon_channel_info *ltc2947_info[] = { HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MAX | HWMON_I_MIN | HWMON_I_RESET_HISTORY | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MAX | HWMON_I_MIN | HWMON_I_RESET_HISTORY | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL), HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MAX | HWMON_C_MIN | HWMON_C_RESET_HISTORY | HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM | HWMON_C_LABEL), HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN | HWMON_P_RESET_HISTORY | HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM | HWMON_P_LABEL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LOWEST | HWMON_T_HIGHEST | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_RESET_HISTORY | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_LABEL, HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_LABEL), NULL }; static const struct hwmon_ops ltc2947_hwmon_ops = { .is_visible = ltc2947_is_visible, .read = ltc2947_read, .write = ltc2947_write, .read_string = ltc2947_read_labels, }; static const struct hwmon_chip_info ltc2947_chip_info = { .ops = <c2947_hwmon_ops, .info = ltc2947_info, }; /* energy attributes are 6bytes wide so we need u64 */ static SENSOR_DEVICE_ATTR(energy1_input, 0444, ltc2947_show_value, NULL, LTC2947_REG_ENERGY1); static SENSOR_DEVICE_ATTR(energy2_input, 0444, ltc2947_show_value, NULL, LTC2947_REG_ENERGY2); static struct attribute *ltc2947_attrs[] = { &sensor_dev_attr_energy1_input.dev_attr.attr, &sensor_dev_attr_energy2_input.dev_attr.attr, NULL, }; ATTRIBUTE_GROUPS(ltc2947); static void ltc2947_clk_disable(void *data) { struct clk *extclk = data; clk_disable_unprepare(extclk); } static int ltc2947_setup(struct ltc2947_data *st) { int ret; struct clk *extclk; u32 dummy, deadband, pol; u32 accum[2]; /* clear status register by reading it */ ret = regmap_read(st->map, LTC2947_REG_STATUS, &dummy); if (ret) return ret; /* * Set max/min for power here since the default values x scale * would overflow on 32bit arch */ ret = ltc2947_val_write(st, LTC2947_REG_POWER_THRE_H, LTC2947_PAGE1, 2, POWER_MAX / 200000); if (ret) return ret; ret = ltc2947_val_write(st, LTC2947_REG_POWER_THRE_L, LTC2947_PAGE1, 2, POWER_MIN / 200000); if (ret) return ret; /* check external clock presence */ extclk = devm_clk_get(st->dev, NULL); if (!IS_ERR(extclk)) { unsigned long rate_hz; u8 pre = 0, div, tbctl; u64 aux; /* let's calculate and set the right valus in TBCTL */ rate_hz = clk_get_rate(extclk); if (rate_hz < LTC2947_CLK_MIN || rate_hz > LTC2947_CLK_MAX) { dev_err(st->dev, "Invalid rate:%lu for external clock", rate_hz); return -EINVAL; } ret = clk_prepare_enable(extclk); if (ret) return ret; ret = devm_add_action_or_reset(st->dev, ltc2947_clk_disable, extclk); if (ret) return ret; /* as in table 1 of the datasheet */ if (rate_hz >= LTC2947_CLK_MIN && rate_hz <= 1000000) pre = 0; else if (rate_hz > 1000000 && rate_hz <= 2000000) pre = 1; else if (rate_hz > 2000000 && rate_hz <= 4000000) pre = 2; else if (rate_hz > 4000000 && rate_hz <= 8000000) pre = 3; else if (rate_hz > 8000000 && rate_hz <= 16000000) pre = 4; else if (rate_hz > 16000000 && rate_hz <= LTC2947_CLK_MAX) pre = 5; /* * Div is given by: * floor(fref / (2^PRE * 32768)) */ div = rate_hz / ((1 << pre) * 32768); tbctl = LTC2947_PRE(pre) | LTC2947_DIV(div); ret = regmap_write(st->map, LTC2947_REG_TBCTL, tbctl); if (ret) return ret; /* * The energy lsb is given by (in W*s): * 06416 * (1/fref) * 2^PRE * (DIV + 1) * The value is multiplied by 10E9 */ aux = (div + 1) * ((1 << pre) * 641600000ULL); st->lsb_energy = DIV_ROUND_CLOSEST_ULL(aux, rate_hz); } else { /* 19.89E-6 * 10E9 */ st->lsb_energy = 19890; } ret = of_property_read_u32_array(st->dev->of_node, "adi,accumulator-ctl-pol", accum, ARRAY_SIZE(accum)); if (!ret) { u32 accum_reg = LTC2947_ACCUM_POL_1(accum[0]) | LTC2947_ACCUM_POL_2(accum[1]); ret = regmap_write(st->map, LTC2947_REG_ACCUM_POL, accum_reg); if (ret) return ret; } ret = of_property_read_u32(st->dev->of_node, "adi,accumulation-deadband-microamp", &deadband); if (!ret) { /* the LSB is the same as the current, so 3mA */ ret = regmap_write(st->map, LTC2947_REG_ACCUM_DEADBAND, deadband / (1000 * 3)); if (ret) return ret; } /* check gpio cfg */ ret = of_property_read_u32(st->dev->of_node, "adi,gpio-out-pol", &pol); if (!ret) { /* setup GPIO as output */ u32 gpio_ctl = LTC2947_GPIO_EN(1) | LTC2947_GPIO_FAN_EN(1) | LTC2947_GPIO_FAN_POL(pol); st->gpio_out = true; ret = regmap_write(st->map, LTC2947_REG_GPIOSTATCTL, gpio_ctl); if (ret) return ret; } ret = of_property_read_u32_array(st->dev->of_node, "adi,gpio-in-accum", accum, ARRAY_SIZE(accum)); if (!ret) { /* * Setup the accum options. The gpioctl is already defined as * input by default. */ u32 accum_val = LTC2947_ACCUM_POL_1(accum[0]) | LTC2947_ACCUM_POL_2(accum[1]); if (st->gpio_out) { dev_err(st->dev, "Cannot have input gpio config if already configured as output"); return -EINVAL; } ret = regmap_write(st->map, LTC2947_REG_GPIO_ACCUM, accum_val); if (ret) return ret; } /* set continuos mode */ return regmap_update_bits(st->map, LTC2947_REG_CTRL, LTC2947_CONT_MODE_MASK, LTC2947_CONT_MODE(1)); } int ltc2947_core_probe(struct regmap *map, const char *name) { struct ltc2947_data *st; struct device *dev = regmap_get_device(map); struct device *hwmon; int ret; st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); if (!st) return -ENOMEM; st->map = map; st->dev = dev; dev_set_drvdata(dev, st); mutex_init(&st->lock); ret = ltc2947_setup(st); if (ret) return ret; hwmon = devm_hwmon_device_register_with_info(dev, name, st, <c2947_chip_info, ltc2947_groups); return PTR_ERR_OR_ZERO(hwmon); } EXPORT_SYMBOL_GPL(ltc2947_core_probe); static int __maybe_unused ltc2947_resume(struct device *dev) { struct ltc2947_data *st = dev_get_drvdata(dev); u32 ctrl = 0; int ret; /* dummy read to wake the device */ ret = regmap_read(st->map, LTC2947_REG_CTRL, &ctrl); if (ret) return ret; /* * Wait for the device. It takes 100ms to wake up so, 10ms extra * should be enough. */ msleep(110); ret = regmap_read(st->map, LTC2947_REG_CTRL, &ctrl); if (ret) return ret; /* ctrl should be 0 */ if (ctrl != 0) { dev_err(st->dev, "Device failed to wake up, ctl:%02X\n", ctrl); return -ETIMEDOUT; } /* set continuous mode */ return regmap_update_bits(st->map, LTC2947_REG_CTRL, LTC2947_CONT_MODE_MASK, LTC2947_CONT_MODE(1)); } static int __maybe_unused ltc2947_suspend(struct device *dev) { struct ltc2947_data *st = dev_get_drvdata(dev); return regmap_update_bits(st->map, LTC2947_REG_CTRL, LTC2947_SHUTDOWN_MASK, 1); } SIMPLE_DEV_PM_OPS(ltc2947_pm_ops, ltc2947_suspend, ltc2947_resume); EXPORT_SYMBOL_GPL(ltc2947_pm_ops); const struct of_device_id ltc2947_of_match[] = { { .compatible = "adi,ltc2947" }, {} }; EXPORT_SYMBOL_GPL(ltc2947_of_match); MODULE_DEVICE_TABLE(of, ltc2947_of_match); MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>"); MODULE_DESCRIPTION("LTC2947 power and energy monitor core driver"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1