// 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 <beniamin.bia@analog.com>"); MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>"); MODULE_DESCRIPTION("Analog Devices ADM1177 ADC driver"); MODULE_LICENSE("GPL v2");