lkml.org 
[lkml]   [2020]   [Jan]   [11]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    /
    Date
    From
    SubjectRe: [PATCH v3 1/3] hwmon: adm1177: Add ADM1177 Hot Swap Controller and Digital Power Monitor driver
    On Tue, Jan 07, 2020 at 12:59:27PM +0200, Beniamin Bia wrote:
    > ADM1177 is a Hot Swap Controller and Digital Power Monitor with
    > Soft Start Pin.
    >
    > Datasheet:
    > Link: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1177.pdf
    >
    > Signed-off-by: Beniamin Bia <beniamin.bia@analog.com>

    Applied to hwmon-next.

    Thanks,
    Guenter

    > ---
    > Changes in v3:
    > -alert_threshold_ua clamped to maximum
    >
    > Documentation/hwmon/adm1177.rst | 36 ++++
    > drivers/hwmon/Kconfig | 10 ++
    > drivers/hwmon/Makefile | 1 +
    > drivers/hwmon/adm1177.c | 288 ++++++++++++++++++++++++++++++++
    > 4 files changed, 335 insertions(+)
    > create mode 100644 Documentation/hwmon/adm1177.rst
    > create mode 100644 drivers/hwmon/adm1177.c
    >
    > diff --git a/Documentation/hwmon/adm1177.rst b/Documentation/hwmon/adm1177.rst
    > new file mode 100644
    > index 000000000000..c81e0b4abd28
    > --- /dev/null
    > +++ b/Documentation/hwmon/adm1177.rst
    > @@ -0,0 +1,36 @@
    > +Kernel driver adm1177
    > +=====================
    > +
    > +Supported chips:
    > + * Analog Devices ADM1177
    > + Prefix: 'adm1177'
    > + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1177.pdf
    > +
    > +Author: Beniamin Bia <beniamin.bia@analog.com>
    > +
    > +
    > +Description
    > +-----------
    > +
    > +This driver supports hardware monitoring for Analog Devices ADM1177
    > +Hot-Swap Controller and Digital Power Monitors with Soft Start Pin.
    > +
    > +
    > +Usage Notes
    > +-----------
    > +
    > +This driver does not auto-detect devices. You will have to instantiate the
    > +devices explicitly. Please see Documentation/i2c/instantiating-devices for
    > +details.
    > +
    > +
    > +Sysfs entries
    > +-------------
    > +
    > +The following attributes are supported. Current maxim attribute
    > +is read-write, all other attributes are read-only.
    > +
    > +in0_input Measured voltage in microvolts.
    > +
    > +curr1_input Measured current in microamperes.
    > +curr1_max_alarm Overcurrent alarm in microamperes.
    > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
    > index 5308c59d7001..3db8f5752675 100644
    > --- a/drivers/hwmon/Kconfig
    > +++ b/drivers/hwmon/Kconfig
    > @@ -164,6 +164,16 @@ config SENSORS_ADM1031
    > This driver can also be built as a module. If so, the module
    > will be called adm1031.
    >
    > +config SENSORS_ADM1177
    > + tristate "Analog Devices ADM1177 and compatibles"
    > + depends on I2C
    > + help
    > + If you say yes here you get support for Analog Devices ADM1177
    > + sensor chips.
    > +
    > + This driver can also be built as a module. If so, the module
    > + will be called adm1177.
    > +
    > config SENSORS_ADM9240
    > tristate "Analog Devices ADM9240 and compatibles"
    > depends on I2C
    > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
    > index 40c036ea45e6..27d04eab1be4 100644
    > --- a/drivers/hwmon/Makefile
    > +++ b/drivers/hwmon/Makefile
    > @@ -34,6 +34,7 @@ obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
    > obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
    > obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o
    > obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
    > +obj-$(CONFIG_SENSORS_ADM1177) += adm1177.o
    > obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
    > obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o
    > obj-$(CONFIG_SENSORS_ADS7871) += ads7871.o
    > diff --git a/drivers/hwmon/adm1177.c b/drivers/hwmon/adm1177.c
    > new file mode 100644
    > index 000000000000..1f2ff7a29306
    > --- /dev/null
    > +++ b/drivers/hwmon/adm1177.c
    > @@ -0,0 +1,288 @@
    > +// 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 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,
    > + const struct i2c_device_id *id)
    > +{
    > + 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 = 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");

    \
     
     \ /
      Last update: 2020-01-11 17:34    [W:5.514 / U:0.048 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site