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.
287 lines
6.3 KiB
287 lines
6.3 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* ADM1177 Hot Swap Controller and Digital Power Monitor with Soft Start Pin |
|
* |
|
* Copyright 2015-2019 Analog Devices Inc. |
|
*/ |
|
|
|
#include <linux/bits.h> |
|
#include <linux/device.h> |
|
#include <linux/hwmon.h> |
|
#include <linux/i2c.h> |
|
#include <linux/init.h> |
|
#include <linux/module.h> |
|
#include <linux/regulator/consumer.h> |
|
|
|
/* Command Byte Operations */ |
|
#define ADM1177_CMD_V_CONT BIT(0) |
|
#define ADM1177_CMD_I_CONT BIT(2) |
|
#define ADM1177_CMD_VRANGE BIT(4) |
|
|
|
/* Extended Register */ |
|
#define ADM1177_REG_ALERT_TH 2 |
|
|
|
#define ADM1177_BITS 12 |
|
|
|
/** |
|
* struct adm1177_state - driver instance specific data |
|
* @client: pointer to i2c client |
|
* @reg: regulator info for the power supply of the device |
|
* @r_sense_uohm: current sense resistor value |
|
* @alert_threshold_ua: current limit for shutdown |
|
* @vrange_high: internal voltage divider |
|
*/ |
|
struct adm1177_state { |
|
struct i2c_client *client; |
|
struct regulator *reg; |
|
u32 r_sense_uohm; |
|
u32 alert_threshold_ua; |
|
bool vrange_high; |
|
}; |
|
|
|
static int adm1177_read_raw(struct adm1177_state *st, u8 num, u8 *data) |
|
{ |
|
return i2c_master_recv(st->client, data, num); |
|
} |
|
|
|
static int adm1177_write_cmd(struct adm1177_state *st, u8 cmd) |
|
{ |
|
return i2c_smbus_write_byte(st->client, cmd); |
|
} |
|
|
|
static int adm1177_write_alert_thr(struct adm1177_state *st, |
|
u32 alert_threshold_ua) |
|
{ |
|
u64 val; |
|
int ret; |
|
|
|
val = 0xFFULL * alert_threshold_ua * st->r_sense_uohm; |
|
val = div_u64(val, 105840000U); |
|
val = div_u64(val, 1000U); |
|
if (val > 0xFF) |
|
val = 0xFF; |
|
|
|
ret = i2c_smbus_write_byte_data(st->client, ADM1177_REG_ALERT_TH, |
|
val); |
|
if (ret) |
|
return ret; |
|
|
|
st->alert_threshold_ua = alert_threshold_ua; |
|
return 0; |
|
} |
|
|
|
static int adm1177_read(struct device *dev, enum hwmon_sensor_types type, |
|
u32 attr, int channel, long *val) |
|
{ |
|
struct adm1177_state *st = dev_get_drvdata(dev); |
|
u8 data[3]; |
|
long dummy; |
|
int ret; |
|
|
|
switch (type) { |
|
case hwmon_curr: |
|
switch (attr) { |
|
case hwmon_curr_input: |
|
ret = adm1177_read_raw(st, 3, data); |
|
if (ret < 0) |
|
return ret; |
|
dummy = (data[1] << 4) | (data[2] & 0xF); |
|
/* |
|
* convert to milliamperes |
|
* ((105.84mV / 4096) x raw) / senseResistor(ohm) |
|
*/ |
|
*val = div_u64((105840000ull * dummy), |
|
4096 * st->r_sense_uohm); |
|
return 0; |
|
case hwmon_curr_max_alarm: |
|
*val = st->alert_threshold_ua; |
|
return 0; |
|
default: |
|
return -EOPNOTSUPP; |
|
} |
|
case hwmon_in: |
|
ret = adm1177_read_raw(st, 3, data); |
|
if (ret < 0) |
|
return ret; |
|
dummy = (data[0] << 4) | (data[2] >> 4); |
|
/* |
|
* convert to millivolts based on resistor devision |
|
* (V_fullscale / 4096) * raw |
|
*/ |
|
if (st->vrange_high) |
|
dummy *= 26350; |
|
else |
|
dummy *= 6650; |
|
|
|
*val = DIV_ROUND_CLOSEST(dummy, 4096); |
|
return 0; |
|
default: |
|
return -EOPNOTSUPP; |
|
} |
|
} |
|
|
|
static int adm1177_write(struct device *dev, enum hwmon_sensor_types type, |
|
u32 attr, int channel, long val) |
|
{ |
|
struct adm1177_state *st = dev_get_drvdata(dev); |
|
|
|
switch (type) { |
|
case hwmon_curr: |
|
switch (attr) { |
|
case hwmon_curr_max_alarm: |
|
adm1177_write_alert_thr(st, val); |
|
return 0; |
|
default: |
|
return -EOPNOTSUPP; |
|
} |
|
default: |
|
return -EOPNOTSUPP; |
|
} |
|
} |
|
|
|
static umode_t adm1177_is_visible(const void *data, |
|
enum hwmon_sensor_types type, |
|
u32 attr, int channel) |
|
{ |
|
const struct adm1177_state *st = data; |
|
|
|
switch (type) { |
|
case hwmon_in: |
|
switch (attr) { |
|
case hwmon_in_input: |
|
return 0444; |
|
} |
|
break; |
|
case hwmon_curr: |
|
switch (attr) { |
|
case hwmon_curr_input: |
|
if (st->r_sense_uohm) |
|
return 0444; |
|
return 0; |
|
case hwmon_curr_max_alarm: |
|
if (st->r_sense_uohm) |
|
return 0644; |
|
return 0; |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
return 0; |
|
} |
|
|
|
static const struct hwmon_channel_info *adm1177_info[] = { |
|
HWMON_CHANNEL_INFO(curr, |
|
HWMON_C_INPUT | HWMON_C_MAX_ALARM), |
|
HWMON_CHANNEL_INFO(in, |
|
HWMON_I_INPUT), |
|
NULL |
|
}; |
|
|
|
static const struct hwmon_ops adm1177_hwmon_ops = { |
|
.is_visible = adm1177_is_visible, |
|
.read = adm1177_read, |
|
.write = adm1177_write, |
|
}; |
|
|
|
static const struct hwmon_chip_info adm1177_chip_info = { |
|
.ops = &adm1177_hwmon_ops, |
|
.info = adm1177_info, |
|
}; |
|
|
|
static void adm1177_remove(void *data) |
|
{ |
|
struct adm1177_state *st = data; |
|
|
|
regulator_disable(st->reg); |
|
} |
|
|
|
static int adm1177_probe(struct i2c_client *client) |
|
{ |
|
struct device *dev = &client->dev; |
|
struct device *hwmon_dev; |
|
struct adm1177_state *st; |
|
u32 alert_threshold_ua; |
|
int ret; |
|
|
|
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); |
|
if (!st) |
|
return -ENOMEM; |
|
|
|
st->client = client; |
|
|
|
st->reg = devm_regulator_get_optional(&client->dev, "vref"); |
|
if (IS_ERR(st->reg)) { |
|
if (PTR_ERR(st->reg) == -EPROBE_DEFER) |
|
return -EPROBE_DEFER; |
|
|
|
st->reg = NULL; |
|
} else { |
|
ret = regulator_enable(st->reg); |
|
if (ret) |
|
return ret; |
|
ret = devm_add_action_or_reset(&client->dev, adm1177_remove, |
|
st); |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
if (device_property_read_u32(dev, "shunt-resistor-micro-ohms", |
|
&st->r_sense_uohm)) |
|
st->r_sense_uohm = 0; |
|
if (device_property_read_u32(dev, "adi,shutdown-threshold-microamp", |
|
&alert_threshold_ua)) { |
|
if (st->r_sense_uohm) |
|
/* |
|
* set maximum default value from datasheet based on |
|
* shunt-resistor |
|
*/ |
|
alert_threshold_ua = div_u64(105840000000, |
|
st->r_sense_uohm); |
|
else |
|
alert_threshold_ua = 0; |
|
} |
|
st->vrange_high = device_property_read_bool(dev, |
|
"adi,vrange-high-enable"); |
|
if (alert_threshold_ua && st->r_sense_uohm) |
|
adm1177_write_alert_thr(st, alert_threshold_ua); |
|
|
|
ret = adm1177_write_cmd(st, ADM1177_CMD_V_CONT | |
|
ADM1177_CMD_I_CONT | |
|
(st->vrange_high ? 0 : ADM1177_CMD_VRANGE)); |
|
if (ret) |
|
return ret; |
|
|
|
hwmon_dev = |
|
devm_hwmon_device_register_with_info(dev, client->name, st, |
|
&adm1177_chip_info, NULL); |
|
return PTR_ERR_OR_ZERO(hwmon_dev); |
|
} |
|
|
|
static const struct i2c_device_id adm1177_id[] = { |
|
{"adm1177", 0}, |
|
{} |
|
}; |
|
MODULE_DEVICE_TABLE(i2c, adm1177_id); |
|
|
|
static const struct of_device_id adm1177_dt_ids[] = { |
|
{ .compatible = "adi,adm1177" }, |
|
{}, |
|
}; |
|
MODULE_DEVICE_TABLE(of, adm1177_dt_ids); |
|
|
|
static struct i2c_driver adm1177_driver = { |
|
.class = I2C_CLASS_HWMON, |
|
.driver = { |
|
.name = "adm1177", |
|
.of_match_table = adm1177_dt_ids, |
|
}, |
|
.probe_new = adm1177_probe, |
|
.id_table = adm1177_id, |
|
}; |
|
module_i2c_driver(adm1177_driver); |
|
|
|
MODULE_AUTHOR("Beniamin Bia <[email protected]>"); |
|
MODULE_AUTHOR("Michael Hennerich <[email protected]>"); |
|
MODULE_DESCRIPTION("Analog Devices ADM1177 ADC driver"); |
|
MODULE_LICENSE("GPL v2");
|
|
|