From nobody Wed Apr 1 11:15:01 2026 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9D23133F360 for ; Mon, 30 Mar 2026 15:14:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774883683; cv=none; b=Xb4tyklI+ntV3FprXDhDHaKgNrE18BJymDc7AP8ayyui+s1l7OoRQjHgsGvG8nuvSHEoCD+pf8deK0qmrvhlV3YKriY4Y4aNqOxZ33U5JB5dQ5qfh4Vdy88cVYvqDsUB7rUftd30zlXqUU1GbIhrMgUgQb3TXluS3c6fxlzv1pM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774883683; c=relaxed/simple; bh=VRDbTGR5KT06TvCCRgaaaJwkEWBKIv4AaAQSnsGCuzk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=HRYNielur5JhGI5eMc1l8rcpX9hWkg59ew+Jt8ms+By6pp5X38eu4moYdqxMozVVSvtfAnBTQ4qcVBo4LjquqXuRlaX+e0tQL+nVipbYy2dXwUP2qTvWQ9Y8QTkIxSTQVa03fH1lWP6YrdDuaYFBk3KhqxpqbiOpOxtv7iyVnIc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=flipper.net; spf=pass smtp.mailfrom=flipper.net; dkim=pass (2048-bit key) header.d=flipper.net header.i=@flipper.net header.b=wqYRXKse; arc=none smtp.client-ip=209.85.128.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=flipper.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flipper.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=flipper.net header.i=@flipper.net header.b="wqYRXKse" Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-48700b1ba53so41115665e9.1 for ; Mon, 30 Mar 2026 08:14:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flipper.net; s=google; t=1774883678; x=1775488478; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=YsVW3bL+27J52svVhSaFhfrbmMBe/k0XtC44DwNiyic=; b=wqYRXKseY5l8k+dXTkme462nB1RNCP+dROmGO422OpBtuNwabgS8OQsoGtkRBW94ek 85u45ANcvmtiABQgpTM9RatnUElW4jL6Cfaxcp3kUoae3aMIYc9zijOU4PxGqJAW9Frg TgkJVY8klIKjKQ18XQUcUzL2rCYlhA9W5XCjkqjABLE53Pl+d7lYYaidTxy2TIt17kI6 hiNq8GV8lOBl/Us8PQIgt5G4Ff/TF1nZHzfVQxM/glwvzpg9sVJvZKxGerRBgU41vXcK T6rIuqxb4iTSQD8sf9ZxgJoxQoPYWFD+WDDxLa1ZbvHvA1RGzd38pWn2gB0cH5nGbya+ zCUg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774883678; x=1775488478; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=YsVW3bL+27J52svVhSaFhfrbmMBe/k0XtC44DwNiyic=; b=sDIJ/IetB9EbwxM3m4IbKMNpWAMyctaR9PlSbHHl1I/3lYo6Tzl2IZQ00MBnoD4mCp /Db93YlYKOOTQir2yk5LeIbv/sqiRsJbKz0+PfpBEBeHVUFyefJa4qggo5mDKMKf8pU1 0AmGj+HsIQII4WfuXPYU1nceJlY+q3CxIJiLR8JtgUwiTwjLinK10wKHLN3doYEb0DfQ KmHs2b1wXrnk1Jnny8JML/mWzjQZ1SG7CsV6/3TLFl6EQ8/ahCCPNU8VcnpMfm7v4G3W UvQv+J9x+h+jcQQQvahMPQSwYfElNftgNHDs6dOdRZt6KDotlahYW3HjP4dVWu9FJrAp J1EQ== X-Forwarded-Encrypted: i=1; AJvYcCVQg8ZhwOeRXAqwT7RcoNZboXvX2ZeJLYAigopcnQKux2Ty9b/cIyyx1iKOJpPZg3E/tmTKJtt5w6ZNebw=@vger.kernel.org X-Gm-Message-State: AOJu0Yzh4kGC9blJotZLv1+1bQqeY90PfhLbGueOjGodv+l5UZ4saYju aHJmJWloCfKJl3M8RcPswfSnfFcpEXurjIzHyPwRC4AdzDGl+NKFdnkfKBO1/eJxVnM= X-Gm-Gg: ATEYQzztb+JPnsuCvorWNdWZMtYNcdKWwqtt8GAVhc/la9n0bGd/GccaJK/dHDX4cCm Xh+HW4CHTDqABVa0JMldshGhh60dvYIzI+UlB/89uvnZ4dQSk6Y6DUdoD/un6ASgRDqMjM454pT ckJmstZN9E56ptL1O1/2HtlBwsOAZqn3lYjB6Zs6ataKKfH6Hn/dLtU/6LfaABrsQdReAexQuLE DSqyNoDPMMLfwPu+XWZe2QKKAljXOK3Wspgjk32VrduYbFjkW+AYSG4msVZwpoCYQfPHKqijVFP 9pwYugqQ7OkFmNgSeu7qDNMBmNZS9oq76Sdp9UxR5P39Lqt00mwpjDHXKeHyaCddpNiRjTqqhmk eEHOZ/5/r6FiUmwZ/kj81E6w31XNvEDFpnHqOMXU+DbVURZhnu2exwPhsX5Bhj/txw3/asnXPLl gNGhbm8y0NWB+wCWVT/gI5C4tpbgbGPNITsIlhLRnrLfgiiUEtkD6UIggY9glO2maoHmTOLM8Yz xCZDA== X-Received: by 2002:a05:600c:4743:b0:485:3d3e:1675 with SMTP id 5b1f17b1804b1-48727f0e5c7mr197783035e9.8.1774883677653; Mon, 30 Mar 2026 08:14:37 -0700 (PDT) Received: from alchark-surface.localdomain (bba-86-98-192-109.alshamil.net.ae. [86.98.192.109]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4873cd7d039sm72764705e9.15.2026.03.30.08.14.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 30 Mar 2026 08:14:37 -0700 (PDT) From: Alexey Charkov Date: Mon, 30 Mar 2026 19:14:16 +0400 Subject: [PATCH v5 2/2] hwmon: Add support for TI INA4230 power monitor Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260330-ina4230-v5-2-eeb322d95b3a@flipper.net> References: <20260330-ina4230-v5-0-eeb322d95b3a@flipper.net> In-Reply-To: <20260330-ina4230-v5-0-eeb322d95b3a@flipper.net> To: Guenter Roeck , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Alexey Charkov X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=31785; i=alchark@flipper.net; h=from:subject:message-id; bh=VRDbTGR5KT06TvCCRgaaaJwkEWBKIv4AaAQSnsGCuzk=; b=owGbwMvMwCW2adGNfoHIK0sZT6slMWSemhzuf1dWSFvnnnrowUbdR4193wNNgxikc08GuVesv izhoZ7YMZGFQYyLwVJMkWXutyW2U434Zu3y8PgKM4eVCWSItEgDAxCwMPDlJuaVGukY6ZlqG+oZ GuoY6xgxcHEKwFRrWzEytF723PribHeXNFfkh3d7D2lWec2N2XpNtVNeYGb2xKYduowM1zWafnX Uv38c+fzZN+f0dw4u51fufav52HBf0Q7D0BtGbAA= X-Developer-Key: i=alchark@flipper.net; a=openpgp; fpr=9DF6A43D95320E9ABA4848F5B2A2D88F1059D4A5 Add a driver for the TI INA4230, a 4-channel power monitor with I2C interface. The driver supports voltage, current, power and energy measurements, but skips the alert functionality in this initial implementation. Signed-off-by: Alexey Charkov --- MAINTAINERS | 1 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ina4230.c | 986 ++++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 999 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index acfa0b0585a5..9fc627b809a4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12579,6 +12579,7 @@ M: Alexey Charkov L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml +F: drivers/hwmon/ina4230.c =20 INDEX OF FURTHER KERNEL DOCUMENTATION M: Carlos Bilbao diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 9d49cfd4ef3d..4649f00f24ca 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2296,6 +2296,17 @@ config SENSORS_INA3221 This driver can also be built as a module. If so, the module will be called ina3221. =20 +config SENSORS_INA4230 + tristate "Texas Instruments INA4230 Quad Current/Voltage Monitor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the TI INA4230 Quad + Current/Voltage Monitor. + + This driver can also be built as a module. If so, the module + will be called ina4230. + config SENSORS_SPD5118 tristate "SPD5118 Compliant Temperature Sensors" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 556e86d277b1..3d83eba94bec 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_SENSORS_INA209) +=3D ina209.o obj-$(CONFIG_SENSORS_INA2XX) +=3D ina2xx.o obj-$(CONFIG_SENSORS_INA238) +=3D ina238.o obj-$(CONFIG_SENSORS_INA3221) +=3D ina3221.o +obj-$(CONFIG_SENSORS_INA4230) +=3D ina4230.o obj-$(CONFIG_SENSORS_INTEL_M10_BMC_HWMON) +=3D intel-m10-bmc-hwmon.o obj-$(CONFIG_SENSORS_ISL28022) +=3D isl28022.o obj-$(CONFIG_SENSORS_IT87) +=3D it87.o diff --git a/drivers/hwmon/ina4230.c b/drivers/hwmon/ina4230.c new file mode 100644 index 000000000000..a31a3f995afc --- /dev/null +++ b/drivers/hwmon/ina4230.c @@ -0,0 +1,986 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * INA4230 Quad Current/Voltage Monitor + * + * Based on INA3221 driver by Texas Instruments Incorporated - https://www= .ti.com/ + * Adapted for INA4230 by Alexey Charkov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INA4230_DRIVER_NAME "ina4230" + +#define INA4230_SHUNT_VOLTAGE_CH1 0x00 +#define INA4230_BUS_VOLTAGE_CH1 0x01 +#define INA4230_CURRENT_CH1 0x02 +#define INA4230_POWER_CH1 0x03 +#define INA4230_ENERGY_CH1 0x04 +#define INA4230_CALIBRATION_CH1 0x05 +#define INA4230_ALERT_LIMIT1 0x06 +#define INA4230_ALERT_CONFIG1 0x07 +#define INA4230_SHUNT_VOLTAGE_CH2 0x08 +#define INA4230_BUS_VOLTAGE_CH2 0x09 +#define INA4230_CURRENT_CH2 0x0A +#define INA4230_POWER_CH2 0x0B +#define INA4230_ENERGY_CH2 0x0C +#define INA4230_CALIBRATION_CH2 0x0D +#define INA4230_ALERT_LIMIT2 0x0E +#define INA4230_ALERT_CONFIG2 0x0F +#define INA4230_SHUNT_VOLTAGE_CH3 0x10 +#define INA4230_BUS_VOLTAGE_CH3 0x11 +#define INA4230_CURRENT_CH3 0x12 +#define INA4230_POWER_CH3 0x13 +#define INA4230_ENERGY_CH3 0x14 +#define INA4230_CALIBRATION_CH3 0x15 +#define INA4230_ALERT_LIMIT3 0x16 +#define INA4230_ALERT_CONFIG3 0x17 +#define INA4230_SHUNT_VOLTAGE_CH4 0x18 +#define INA4230_BUS_VOLTAGE_CH4 0x19 +#define INA4230_CURRENT_CH4 0x1A +#define INA4230_POWER_CH4 0x1B +#define INA4230_ENERGY_CH4 0x1C +#define INA4230_CALIBRATION_CH4 0x1D +#define INA4230_ALERT_LIMIT4 0x1E +#define INA4230_ALERT_CONFIG4 0x1F +#define INA4230_CONFIG1 0x20 +#define INA4230_CONFIG2 0x21 +#define INA4230_FLAGS 0x22 +#define INA4230_MANUFACTURER_ID 0x7E + +#define INA4230_CALIBRATION_MASK GENMASK(14, 0) + +#define INA4230_ALERT_CHANNEL_MASK GENMASK(4, 3) +#define INA4230_ALERT_MASK GENMASK(2, 0) +/* Shunt voltage over limit */ +#define INA4230_ALERT_MASK_SOL 0x1 +/* Shunt voltage under limit */ +#define INA4230_ALERT_MASK_SUL 0x2 +/* Bus voltage over limit */ +#define INA4230_ALERT_MASK_BOL 0x3 +/* Bus voltage under limit */ +#define INA4230_ALERT_MASK_BUL 0x4 +/* Power over limit */ +#define INA4230_ALERT_MASK_POL 0x5 + +#define INA4230_CONFIG1_ACTIVE_CHANNEL_MASK GENMASK(15, 12) +#define INA4230_CONFIG1_AVG_MASK GENMASK(11, 9) +#define INA4230_CONFIG1_VBUSCT_MASK GENMASK(8, 6) +#define INA4230_CONFIG1_VSHCT_MASK GENMASK(5, 3) +#define INA4230_CONFIG1_MODE_MASK GENMASK(2, 0) +#define INA4230_MODE_POWERDOWN 0 +#define INA4230_MODE_SHUNT_SINGLE 1 +#define INA4230_MODE_BUS_SINGLE 2 +#define INA4230_MODE_BUS_SHUNT_SINGLE 3 +#define INA4230_MODE_POWERDOWN1 4 +#define INA4230_MODE_SHUNT_CONTINUOUS 5 +#define INA4230_MODE_BUS_CONTINUOUS 6 +#define INA4230_MODE_BUS_SHUNT_CONTINUOUS 7 + +#define INA4230_CONFIG2_RST BIT(15) +#define INA4230_CONFIG2_ACC_RST_MASK GENMASK(11, 8) +#define INA4230_CONFIG2_CNVR_MASK BIT(7) +#define INA4230_CONFIG2_ENOF_MASK BIT(6) +#define INA4230_CONFIG2_ALERT_LATCH BIT(5) +#define INA4230_CONFIG2_ALERT_POL BIT(4) +#define INA4230_CONFIG2_RANGE_MASK GENMASK(3, 0) +#define INA4230_CONFIG2_RANGE_CH(x) \ + FIELD_PREP(INA4230_CONFIG2_RANGE_MASK, BIT((x))) + +#define INA4230_FLAGS_LIMIT4_ALERT BIT(15) +#define INA4230_FLAGS_LIMIT3_ALERT BIT(14) +#define INA4230_FLAGS_LIMIT2_ALERT BIT(13) +#define INA4230_FLAGS_LIMIT1_ALERT BIT(12) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH4 BIT(11) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH3 BIT(10) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH2 BIT(9) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH1 BIT(8) +#define INA4230_FLAGS_CVRF BIT(7) +#define INA4230_FLAGS_MATH_OVERFLOW BIT(6) + +#define INA4230_RSHUNT_DEFAULT 10000 +#define INA4230_CONFIG_DEFAULT \ + (FIELD_PREP(INA4230_CONFIG1_ACTIVE_CHANNEL_MASK, 0xF) | \ + FIELD_PREP(INA4230_CONFIG1_AVG_MASK, 0x1) | \ + FIELD_PREP(INA4230_CONFIG1_VBUSCT_MASK, 0x4) | \ + FIELD_PREP(INA4230_CONFIG1_VSHCT_MASK, 0x4) | \ + FIELD_PREP(INA4230_CONFIG1_MODE_MASK, 0x7)) +#define INA4230_CONFIG_CHx_EN(x) \ + FIELD_PREP(INA4230_CONFIG1_ACTIVE_CHANNEL_MASK, BIT((x))) + +enum ina4230_fields { + /* Alert configuration settings: channel masks */ + F_ALERT1_CH, F_ALERT2_CH, F_ALERT3_CH, F_ALERT4_CH, + /* Alert configuration settings: alert masks */ + F_ALERT1_TYPE, F_ALERT2_TYPE, F_ALERT3_TYPE, F_ALERT4_TYPE, + /* Configuration registers */ + F_CH_EN, F_AVG, F_VBUSCT, F_VSHCT, F_MODE, + F_RST, F_ACC_RST, F_CNV_ALERT, F_ENOF, F_ALERT_LATCH, F_ALERT_POL, F_RANG= E, + /* Status flags */ + F_LIMIT1_ALERT, F_LIMIT2_ALERT, F_LIMIT3_ALERT, F_LIMIT4_ALERT, + F_ENERGY_OVERFLOW_CH1, F_ENERGY_OVERFLOW_CH2, F_ENERGY_OVERFLOW_CH3, F_EN= ERGY_OVERFLOW_CH4, + F_CVRF, F_MATH_OVERFLOW, + /* sentinel */ + F_MAX_FIELDS +}; + +static const struct reg_field ina4230_reg_fields[] =3D { + [F_ALERT1_CH] =3D REG_FIELD(INA4230_ALERT_CONFIG1, 3, 4), + [F_ALERT2_CH] =3D REG_FIELD(INA4230_ALERT_CONFIG2, 3, 4), + [F_ALERT3_CH] =3D REG_FIELD(INA4230_ALERT_CONFIG3, 3, 4), + [F_ALERT4_CH] =3D REG_FIELD(INA4230_ALERT_CONFIG4, 3, 4), + + [F_ALERT1_TYPE] =3D REG_FIELD(INA4230_ALERT_CONFIG1, 0, 2), + [F_ALERT2_TYPE] =3D REG_FIELD(INA4230_ALERT_CONFIG2, 0, 2), + [F_ALERT3_TYPE] =3D REG_FIELD(INA4230_ALERT_CONFIG3, 0, 2), + [F_ALERT4_TYPE] =3D REG_FIELD(INA4230_ALERT_CONFIG4, 0, 2), + + [F_CH_EN] =3D REG_FIELD(INA4230_CONFIG1, 12, 15), + [F_AVG] =3D REG_FIELD(INA4230_CONFIG1, 9, 11), + [F_VBUSCT] =3D REG_FIELD(INA4230_CONFIG1, 6, 8), + [F_VSHCT] =3D REG_FIELD(INA4230_CONFIG1, 3, 5), + [F_MODE] =3D REG_FIELD(INA4230_CONFIG1, 0, 2), + [F_RST] =3D REG_FIELD(INA4230_CONFIG2, 15, 15), + [F_ACC_RST] =3D REG_FIELD(INA4230_CONFIG2, 8, 11), + [F_CNV_ALERT] =3D REG_FIELD(INA4230_CONFIG2, 7, 7), + [F_ENOF] =3D REG_FIELD(INA4230_CONFIG2, 6, 6), + [F_ALERT_LATCH] =3D REG_FIELD(INA4230_CONFIG2, 5, 5), + [F_ALERT_POL] =3D REG_FIELD(INA4230_CONFIG2, 4, 4), + [F_RANGE] =3D REG_FIELD(INA4230_CONFIG2, 0, 3), + + [F_LIMIT1_ALERT] =3D REG_FIELD(INA4230_FLAGS, 12, 12), + [F_LIMIT2_ALERT] =3D REG_FIELD(INA4230_FLAGS, 13, 13), + [F_LIMIT3_ALERT] =3D REG_FIELD(INA4230_FLAGS, 14, 14), + [F_LIMIT4_ALERT] =3D REG_FIELD(INA4230_FLAGS, 15, 15), + [F_ENERGY_OVERFLOW_CH1] =3D REG_FIELD(INA4230_FLAGS, 8, 8), + [F_ENERGY_OVERFLOW_CH2] =3D REG_FIELD(INA4230_FLAGS, 9, 9), + [F_ENERGY_OVERFLOW_CH3] =3D REG_FIELD(INA4230_FLAGS, 10, 10), + [F_ENERGY_OVERFLOW_CH4] =3D REG_FIELD(INA4230_FLAGS, 11, 11), + [F_CVRF] =3D REG_FIELD(INA4230_FLAGS, 7, 7), + [F_MATH_OVERFLOW] =3D REG_FIELD(INA4230_FLAGS, 6, 6), +}; + +enum ina4230_channels { + INA4230_CHANNEL1, + INA4230_CHANNEL2, + INA4230_CHANNEL3, + INA4230_CHANNEL4, + INA4230_NUM_CHANNELS +}; + +/** + * struct ina4230_input - channel input source specific information + * @label: label of channel input source + * @shunt_resistor: shunt resistor value of channel input source + * @shunt_gain: gain of shunt voltage for current calculation + * @max_expected_current: maximum expected current in micro-Ampere for ADC + * calibration + * @current_lsb_uA: current LSB in micro-Amperes + * @disconnected: connection status of channel input source + */ +struct ina4230_input { + const char *label; + int shunt_resistor; + int shunt_gain; + int max_expected_current; + int current_lsb_uA; + bool disconnected; +}; + +/** + * struct ina4230_data - device specific information + * @pm_dev: Device pointer for pm runtime + * @regmap: Register map of the device + * @fields: Register fields of the device + * @inputs: Array of channel input source specific structures + * @reg_config1: cached value of CONFIG1 register + * @reg_config2: cached value of CONFIG2 register + * @alert_active_high: flag indicating alert polarity is active high + */ +struct ina4230_data { + struct device *pm_dev; + struct regmap *regmap; + struct regmap_field *fields[F_MAX_FIELDS]; + struct ina4230_input inputs[INA4230_NUM_CHANNELS]; + unsigned int reg_config1; + unsigned int reg_config2; + bool alert_active_high; +}; + +static inline bool ina4230_is_enabled(struct ina4230_data *ina, int channe= l) +{ + return pm_runtime_active(ina->pm_dev) && + !ina->inputs[channel].disconnected && + ina->reg_config1 & INA4230_CONFIG_CHx_EN(channel); +} + +/* Lookup table for Bus and Shunt conversion times in usec */ +static const u16 ina4230_conv_time[] =3D { + 140, 204, 332, 588, 1100, 2116, 4156, 8244, +}; + +/* Lookup table for number of samples used in averaging mode */ +static const int ina4230_avg_samples[] =3D { + 1, 4, 16, 64, 128, 256, 512, 1024, +}; + +/* Converting update_interval in msec to conversion time in usec */ +static inline u32 ina4230_interval_ms_to_conv_time(u16 config, int interva= l) +{ + u32 channels =3D hweight16(config & INA4230_CONFIG1_ACTIVE_CHANNEL_MASK); + u32 samples_idx =3D FIELD_GET(INA4230_CONFIG1_AVG_MASK, config); + u32 samples =3D ina4230_avg_samples[samples_idx]; + + if (!channels) + return U32_MAX; + + /* Bisect the result to Bus and Shunt conversion times */ + return DIV_ROUND_CLOSEST(interval * 1000 / 2, channels * samples); +} + +/* Converting CONFIG register value to update_interval in usec */ +static inline u32 ina4230_reg_to_interval_us(u16 config) +{ + u32 channels =3D hweight16(config & INA4230_CONFIG1_ACTIVE_CHANNEL_MASK); + u32 vbus_ct_idx =3D FIELD_GET(INA4230_CONFIG1_VBUSCT_MASK, config); + u32 vsh_ct_idx =3D FIELD_GET(INA4230_CONFIG1_VSHCT_MASK, config); + u32 vbus_ct =3D ina4230_conv_time[vbus_ct_idx]; + u32 vsh_ct =3D ina4230_conv_time[vsh_ct_idx]; + + /* Calculate total conversion time */ + return channels * (vbus_ct + vsh_ct); +} + +static const u8 ina4230_calibration_reg[] =3D { + INA4230_CALIBRATION_CH1, + INA4230_CALIBRATION_CH2, + INA4230_CALIBRATION_CH3, + INA4230_CALIBRATION_CH4, +}; + +static int ina4230_set_calibration(struct ina4230_data *ina, int channel) +{ + struct ina4230_input *input =3D &ina->inputs[channel]; + u8 reg =3D ina4230_calibration_reg[channel]; + int shunt_range_uV, ret; + u32 calibration; + u64 n, d; + + shunt_range_uV =3D mult_frac(input->max_expected_current, + input->shunt_resistor, + 1000000); + input->shunt_gain =3D shunt_range_uV > 20480 ? 1 : 4; + ina->reg_config2 &=3D ~INA4230_CONFIG2_RANGE_CH(channel); + if (input->shunt_gain =3D=3D 4) + ina->reg_config2 |=3D INA4230_CONFIG2_RANGE_CH(channel); + + ret =3D regmap_write(ina->regmap, INA4230_CONFIG2, ina->reg_config2); + if (ret) + return ret; + + input->current_lsb_uA =3D DIV_ROUND_UP(input->max_expected_current, 32768= ); + n =3D 5120000000ULL; + d =3D (u64)input->current_lsb_uA * input->shunt_resistor * input->shunt_g= ain; + /* Ensure rounding to the closest integer */ + n +=3D d / 2; + n =3D div64_u64(n, d); + if (n > INA4230_CALIBRATION_MASK) { + dev_err(ina->pm_dev, + "Shunt %duOhm too low for expected current %duA, cannot calibrate chann= el %d\n", + input->shunt_resistor, input->max_expected_current, channel + 1); + return -ERANGE; + } + + calibration =3D n & INA4230_CALIBRATION_MASK; + + return regmap_write(ina->regmap, reg, calibration); +} + +static const u8 ina4230_in_reg[] =3D { + INA4230_BUS_VOLTAGE_CH1, + INA4230_BUS_VOLTAGE_CH2, + INA4230_BUS_VOLTAGE_CH3, + INA4230_BUS_VOLTAGE_CH4, + INA4230_SHUNT_VOLTAGE_CH1, + INA4230_SHUNT_VOLTAGE_CH2, + INA4230_SHUNT_VOLTAGE_CH3, + INA4230_SHUNT_VOLTAGE_CH4, +}; + +static const u8 ina4230_curr_reg[][INA4230_NUM_CHANNELS] =3D { + [hwmon_curr_input] =3D { INA4230_CURRENT_CH1, INA4230_CURRENT_CH2, + INA4230_CURRENT_CH3, INA4230_CURRENT_CH4 }, +}; + +static const u8 ina4230_power_reg[] =3D { + INA4230_POWER_CH1, INA4230_POWER_CH2, INA4230_POWER_CH3, INA4230_POWER_CH4 +}; + +static const u8 ina4230_energy_reg[] =3D { + INA4230_ENERGY_CH1, INA4230_ENERGY_CH2, + INA4230_ENERGY_CH3, INA4230_ENERGY_CH4 +}; + +static int ina4230_read_chip(struct device *dev, u32 attr, long *val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int regval; + + switch (attr) { + case hwmon_chip_samples: + regval =3D FIELD_GET(INA4230_CONFIG1_AVG_MASK, ina->reg_config1); + *val =3D ina4230_avg_samples[regval]; + return 0; + case hwmon_chip_update_interval: + /* Return in msec */ + *val =3D ina4230_reg_to_interval_us(ina->reg_config1); + *val =3D DIV_ROUND_CLOSEST(*val, 1000); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_in(struct device *dev, u32 attr, int channel, long= *val) +{ + const bool is_shunt =3D channel > INA4230_CHANNEL4; + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u8 reg =3D ina4230_in_reg[channel]; + int regval, ret; + + /* + * Translate shunt channel index to sensor channel index + */ + channel %=3D INA4230_NUM_CHANNELS; + + switch (attr) { + case hwmon_in_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret =3D regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + /* + * Scale of shunt voltage (uV): LSB is 2.5uV or 625nV + * depending on gain setting + * Scale of bus voltage (mV): LSB is 1.6mV + */ + if (is_shunt) + *val =3D mult_frac((long)(int16_t)regval, + 2500 / ina->inputs[channel].shunt_gain, + 1000000); + else + *val =3D mult_frac((long)(int16_t)regval, + 1600, + 1000); + return 0; + case hwmon_in_enable: + *val =3D ina4230_is_enabled(ina, channel); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_power(struct device *dev, u32 attr, int channel, l= ong *val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u8 reg =3D ina4230_power_reg[channel]; + int regval, ret; + + switch (attr) { + case hwmon_power_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret =3D regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val =3D (int16_t)regval * + (long)ina->inputs[channel].current_lsb_uA * 32; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_energy(struct device *dev, u32 attr, int channel, = long *val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u8 reg =3D ina4230_energy_reg[channel]; + int ret; + __be32 regval; + + switch (attr) { + case hwmon_energy_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret =3D regmap_noinc_read(ina->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val =3D be32_to_cpu(regval) * + (long)ina->inputs[channel].current_lsb_uA * 32; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_curr(struct device *dev, u32 attr, + int channel, long *val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u8 reg =3D ina4230_curr_reg[attr][channel]; + int regval, ret; + + switch (attr) { + case hwmon_curr_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret =3D regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val =3D (int16_t)regval * + (long)ina->inputs[channel].current_lsb_uA / 1000; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_write_chip(struct device *dev, u32 attr, long val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int idx; + u32 tmp; + + switch (attr) { + case hwmon_chip_samples: + idx =3D find_closest(val, ina4230_avg_samples, + ARRAY_SIZE(ina4230_avg_samples)); + + FIELD_MODIFY(INA4230_CONFIG1_AVG_MASK, &ina->reg_config1, idx); + return regmap_write(ina->regmap, INA4230_CONFIG1, ina->reg_config1); + case hwmon_chip_update_interval: + tmp =3D ina4230_interval_ms_to_conv_time(ina->reg_config1, val); + idx =3D find_closest(tmp, ina4230_conv_time, + ARRAY_SIZE(ina4230_conv_time)); + + FIELD_MODIFY(INA4230_CONFIG1_VBUSCT_MASK, &ina->reg_config1, idx); + FIELD_MODIFY(INA4230_CONFIG1_VSHCT_MASK, &ina->reg_config1, idx); + return regmap_write(ina->regmap, INA4230_CONFIG1, ina->reg_config1); + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_write_enable(struct device *dev, int channel, bool enab= le) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u16 config, mask =3D INA4230_CONFIG_CHx_EN(channel); + u16 config_old =3D ina->reg_config1 & mask; + u32 tmp; + int ret; + + config =3D enable ? mask : 0; + + /* Bypass if enable status is not being changed */ + if (config_old =3D=3D config) + return 0; + + /* For enabling routine, increase refcount and resume() at first */ + if (enable) { + ret =3D pm_runtime_resume_and_get(ina->pm_dev); + if (ret < 0) { + dev_err(dev, "Failed to get PM runtime\n"); + return ret; + } + } + + /* Enable or disable the channel */ + tmp =3D (ina->reg_config1 & ~mask) | (config & mask); + ret =3D regmap_write(ina->regmap, INA4230_CONFIG1, tmp); + if (ret) + goto fail; + + /* Cache the latest config register value */ + ina->reg_config1 =3D tmp; + + /* For disabling routine, decrease refcount or suspend() at last */ + if (!enable) + pm_runtime_put_sync(ina->pm_dev); + + return 0; + +fail: + if (enable) { + dev_err(dev, "Failed to enable channel %d: error %d\n", + channel, ret); + pm_runtime_put_sync(ina->pm_dev); + } + + return ret; +} + +static int ina4230_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_chip: + ret =3D ina4230_read_chip(dev, attr, val); + break; + case hwmon_in: + /* 0-align channel ID */ + ret =3D ina4230_read_in(dev, attr, channel - 1, val); + break; + case hwmon_curr: + ret =3D ina4230_read_curr(dev, attr, channel, val); + break; + case hwmon_power: + ret =3D ina4230_read_power(dev, attr, channel, val); + break; + case hwmon_energy: + ret =3D ina4230_read_energy(dev, attr, channel, val); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + return ret; +} + +static int ina4230_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_chip: + ret =3D ina4230_write_chip(dev, attr, val); + break; + case hwmon_in: + /* 0-align channel ID */ + ret =3D ina4230_write_enable(dev, channel - 1, val); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + return ret; +} + +static int ina4230_read_string(struct device *dev, enum hwmon_sensor_types= type, + u32 attr, int channel, const char **str) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int index =3D channel - 1; + + *str =3D ina->inputs[index].label; + + return 0; +} + +static umode_t ina4230_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct ina4230_data *ina =3D drvdata; + const struct ina4230_input *input =3D NULL; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_samples: + case hwmon_chip_update_interval: + return 0644; + default: + return 0; + } + case hwmon_in: + /* Ignore in0_ */ + if (channel =3D=3D 0) + return 0; + + switch (attr) { + case hwmon_in_label: + if (channel - 1 <=3D INA4230_CHANNEL4) + input =3D &ina->inputs[channel - 1]; + /* Hide label node if label is not provided */ + return (input && input->label) ? 0444 : 0; + case hwmon_in_input: + return 0444; + case hwmon_in_enable: + return 0644; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + return 0444; + default: + return 0; + } + case hwmon_power: + switch (attr) { + case hwmon_power_input: + return 0444; + default: + return 0; + } + case hwmon_energy: + switch (attr) { + case hwmon_energy_input: + return 0444; + default: + return 0; + } + default: + return 0; + } +} + +static const struct hwmon_channel_info * const ina4230_info[] =3D { + HWMON_CHANNEL_INFO(chip, + HWMON_C_SAMPLES, + HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(in, + /* 0: dummy, skipped in is_visible */ + HWMON_I_INPUT, + /* 1-4: input voltage Channels */ + HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, + /* 5-8: shunt voltage Channels */ + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT), + HWMON_CHANNEL_INFO(curr, + /* 1-4: current channels*/ + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + /* 1-4: power channels*/ + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT), + HWMON_CHANNEL_INFO(energy, + /* 1-4: energy channels*/ + HWMON_E_INPUT, + HWMON_E_INPUT, + HWMON_E_INPUT, + HWMON_E_INPUT), + NULL +}; + +static const struct hwmon_ops ina4230_hwmon_ops =3D { + .is_visible =3D ina4230_is_visible, + .read_string =3D ina4230_read_string, + .read =3D ina4230_read, + .write =3D ina4230_write, +}; + +static const struct hwmon_chip_info ina4230_chip_info =3D { + .ops =3D &ina4230_hwmon_ops, + .info =3D ina4230_info, +}; + +static const struct regmap_range ina4230_vol_ranges[] =3D { + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH1, INA4230_ENERGY_CH1), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH2, INA4230_ENERGY_CH2), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH3, INA4230_ENERGY_CH3), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH4, INA4230_ENERGY_CH4), + regmap_reg_range(INA4230_CONFIG2, INA4230_CONFIG2), + regmap_reg_range(INA4230_FLAGS, INA4230_FLAGS), +}; + +static const struct regmap_access_table ina4230_volatile_table =3D { + .yes_ranges =3D ina4230_vol_ranges, + .n_yes_ranges =3D ARRAY_SIZE(ina4230_vol_ranges), +}; + +static const struct regmap_config ina4230_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 16, + + .cache_type =3D REGCACHE_MAPLE, + .volatile_table =3D &ina4230_volatile_table, +}; + +static int ina4230_probe_child_from_dt(struct device *dev, + struct device_node *child, + struct ina4230_data *ina) +{ + struct ina4230_input *input; + u32 val; + int ret; + + ret =3D of_property_read_u32(child, "reg", &val); + if (ret) + return dev_err_probe(dev, ret, + "missing reg property of %pOFn\n", child); + else if (val > INA4230_CHANNEL4) + return dev_err_probe(dev, -EINVAL, + "invalid reg %d of %pOFn\n", val, child); + + input =3D &ina->inputs[val]; + + /* Log the disconnected channel input */ + if (!of_device_is_available(child)) { + input->disconnected =3D true; + return 0; + } + + /* Save the connected input label if available */ + of_property_read_string(child, "label", &input->label); + + /* Overwrite default shunt resistor value optionally */ + if (!of_property_read_u32(child, "shunt-resistor-micro-ohms", &val)) { + if (val < 1 || val > INT_MAX) + return dev_err_probe(dev, -EINVAL, + "invalid shunt resistor value %u of %pOFn\n", + val, child); + + input->shunt_resistor =3D val; + } + + /* Save the expected maxcurrent */ + if (!of_property_read_u32(child, "ti,maximum-expected-current-microamp", = &val)) { + if (val < 32768 || val > INT_MAX) + return dev_err_probe(dev, -EINVAL, + "invalid max current value %u of %pOFn\n", + val, child); + + input->max_expected_current =3D val; + } + + return 0; +} + +static int ina4230_probe_from_dt(struct device *dev, struct ina4230_data *= ina) +{ + const struct device_node *np =3D dev->of_node; + int ret; + + /* Compatible with non-DT platforms */ + if (!np) + return 0; + + ina->alert_active_high =3D of_property_read_bool(np, "ti,alert-polarity-a= ctive-high"); + + for_each_child_of_node_scoped(np, child) { + ret =3D ina4230_probe_child_from_dt(dev, child, ina); + if (ret) + return ret; + } + + ret =3D devm_regulator_get_enable_optional(dev, "vs"); + if (ret && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + return 0; +} + +static int ina4230_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct ina4230_data *ina; + struct device *hwmon_dev; + int i, ret; + + ina =3D devm_kzalloc(dev, sizeof(*ina), GFP_KERNEL); + if (!ina) + return -ENOMEM; + + ina->regmap =3D devm_regmap_init_i2c(client, &ina4230_regmap_config); + if (IS_ERR(ina->regmap)) + return PTR_ERR(ina->regmap); + + ret =3D devm_regmap_field_bulk_alloc(dev, ina->regmap, ina->fields, + ina4230_reg_fields, + ARRAY_SIZE(ina4230_reg_fields)); + if (ret) + return ret; + + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + ina->inputs[i].shunt_resistor =3D INA4230_RSHUNT_DEFAULT; + /* Default for 1mA LSB current measurements */ + ina->inputs[i].max_expected_current =3D 32768000; + } + + ret =3D ina4230_probe_from_dt(dev, ina); + if (ret) + return dev_err_probe(dev, ret, + "Unable to probe from device tree\n"); + + /* The driver will be reset, so use reset value */ + ina->reg_config1 =3D INA4230_CONFIG_DEFAULT; + ina->reg_config2 =3D 0; + + if (ina->alert_active_high) + FIELD_MODIFY(INA4230_CONFIG2_ALERT_POL, &ina->reg_config2, 1); + + /* Disable channels if their inputs are disconnected */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (ina->inputs[i].disconnected) + ina->reg_config1 &=3D ~INA4230_CONFIG_CHx_EN(i); + } + + ina->pm_dev =3D dev; + dev_set_drvdata(dev, ina); + + /* Enable PM runtime -- status is suspended by default */ + pm_runtime_enable(ina->pm_dev); + + /* Initialize (resume) the device */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (ina->inputs[i].disconnected) + continue; + + /* Match the refcount with number of enabled channels */ + ret =3D pm_runtime_get_sync(ina->pm_dev); + if (ret < 0) + goto fail; + } + + /* Set calibration values after device resume/reset */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) { + ret =3D ina4230_set_calibration(ina, i); + if (ret) + goto fail; + } + } + + hwmon_dev =3D devm_hwmon_device_register_with_info(dev, client->name, ina, + &ina4230_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) { + ret =3D dev_err_probe(dev, PTR_ERR(hwmon_dev), + "Unable to register hwmon device\n"); + goto fail; + } + + return 0; + +fail: + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + /* pm_runtime_put_noidle() for connected channels to balance get_sync */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected && + ina->reg_config1 & INA4230_CONFIG_CHx_EN(i)) + pm_runtime_put_noidle(ina->pm_dev); + } + + return ret; +} + +static void ina4230_remove(struct i2c_client *client) +{ + struct ina4230_data *ina =3D dev_get_drvdata(&client->dev); + int i; + + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + + /* pm_runtime_put_noidle() for connected channels to balance get_sync */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected && + ina->reg_config1 & INA4230_CONFIG_CHx_EN(i)) + pm_runtime_put_noidle(ina->pm_dev); + } +} + +static int ina4230_suspend(struct device *dev) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int ret; + + /* Save config register value and enable cache-only */ + ret =3D regmap_read(ina->regmap, INA4230_CONFIG1, &ina->reg_config1); + if (ret) + return ret; + + regcache_cache_only(ina->regmap, true); + regcache_mark_dirty(ina->regmap); + + return 0; +} + +static int ina4230_resume(struct device *dev) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int ret; + + regcache_cache_only(ina->regmap, false); + + /* Software reset the chip */ + ret =3D regmap_field_write(ina->fields[F_RST], true); + if (ret) { + dev_err(dev, "Unable to reset device\n"); + return ret; + } + + /* Restore cached register values to hardware */ + ret =3D regcache_sync(ina->regmap); + if (ret) + return ret; + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ina4230_pm, ina4230_suspend, ina4230_resu= me, + NULL); + +static const struct of_device_id ina4230_of_match_table[] =3D { + { .compatible =3D "ti,ina4230", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ina4230_of_match_table); + +static const struct i2c_device_id ina4230_ids[] =3D { + { "ina4230" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, ina4230_ids); + +static struct i2c_driver ina4230_i2c_driver =3D { + .probe =3D ina4230_probe, + .remove =3D ina4230_remove, + .driver =3D { + .name =3D INA4230_DRIVER_NAME, + .of_match_table =3D ina4230_of_match_table, + .pm =3D pm_ptr(&ina4230_pm), + }, + .id_table =3D ina4230_ids, +}; +module_i2c_driver(ina4230_i2c_driver); + +MODULE_AUTHOR("Alexey Charkov "); +MODULE_DESCRIPTION("Texas Instruments INA4230 HWMon Driver"); +MODULE_LICENSE("GPL"); --=20 2.52.0