forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1183 lines
30 KiB
1183 lines
30 KiB
// 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 <[email protected]>"); |
|
MODULE_DESCRIPTION("LTC2947 power and energy monitor core driver"); |
|
MODULE_LICENSE("GPL");
|
|
|