File Index
Files and directories
Sign in
FILE

ina233.c

File preview with syntax highlighting.

linux-6.19.14/drivers/hwmon/pmbus/ina233.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Hardware monitoring driver for ina233
  4 *
  5 * Copyright (c) 2025 Leo Yang
  6 */
  7
  8#include <linux/err.h>
  9#include <linux/i2c.h>
 10#include <linux/init.h>
 11#include <linux/kernel.h>
 12#include <linux/module.h>
 13#include "pmbus.h"
 14
 15#define MFR_READ_VSHUNT 0xd1
 16#define MFR_CALIBRATION 0xd4
 17
 18#define INA233_MAX_CURRENT_DEFAULT	32768000 /* uA */
 19#define INA233_RSHUNT_DEFAULT		2000 /* uOhm */
 20
 21#define MAX_M_VAL 32767
 22
 23static void calculate_coef(int *m, int *R, u32 current_lsb, int power_coef)
 24{
 25	u64 scaled_m;
 26	int scale_factor = 0;
 27	int scale_coef = 1;
 28
 29	/*
 30	 * 1000000 from Current_LSB A->uA .
 31	 * scale_coef is for scaling up to minimize rounding errors,
 32	 * If there is no decimal information, no need to scale.
 33	 */
 34	if (1000000 % current_lsb) {
 35		/* Scaling to keep integer precision */
 36		scale_factor = -3;
 37		scale_coef = 1000;
 38	}
 39
 40	/*
 41	 * Unit Conversion (Current_LSB A->uA) and use scaling(scale_factor)
 42	 * to keep integer precision.
 43	 * Formulae referenced from spec.
 44	 */
 45	scaled_m = div64_u64(1000000 * scale_coef, (u64)current_lsb * power_coef);
 46
 47	/* Maximize while keeping it bounded.*/
 48	while (scaled_m > MAX_M_VAL) {
 49		scaled_m = div_u64(scaled_m, 10);
 50		scale_factor++;
 51	}
 52	/* Scale up only if fractional part exists. */
 53	while (scaled_m * 10 < MAX_M_VAL && scale_coef != 1) {
 54		scaled_m *= 10;
 55		scale_factor--;
 56	}
 57
 58	*m = scaled_m;
 59	*R = scale_factor;
 60}
 61
 62static int ina233_read_word_data(struct i2c_client *client, int page,
 63				 int phase, int reg)
 64{
 65	int ret;
 66
 67	switch (reg) {
 68	case PMBUS_VIRT_READ_VMON:
 69		ret = pmbus_read_word_data(client, 0, 0xff, MFR_READ_VSHUNT);
 70		if (ret < 0)
 71			return ret;
 72
 73		/* Adjust returned value to match VIN coefficients */
 74		/* VIN: 1.25 mV VSHUNT: 2.5 uV LSB */
 75		ret = clamp_val(DIV_ROUND_CLOSEST((s16)ret * 25, 12500),
 76				S16_MIN, S16_MAX) & 0xffff;
 77		break;
 78	default:
 79		ret = -ENODATA;
 80		break;
 81	}
 82	return ret;
 83}
 84
 85static int ina233_probe(struct i2c_client *client)
 86{
 87	struct device *dev = &client->dev;
 88	int ret, m, R;
 89	u32 rshunt;
 90	u32 max_current;
 91	u32 current_lsb;
 92	u16 calibration;
 93	struct pmbus_driver_info *info;
 94
 95	info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info),
 96			    GFP_KERNEL);
 97	if (!info)
 98		return -ENOMEM;
 99
100	info->pages = 1;
101	info->format[PSC_VOLTAGE_IN] = direct;
102	info->format[PSC_VOLTAGE_OUT] = direct;
103	info->format[PSC_CURRENT_OUT] = direct;
104	info->format[PSC_POWER] = direct;
105	info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_INPUT
106		| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
107		| PMBUS_HAVE_POUT
108		| PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON;
109	info->m[PSC_VOLTAGE_IN] = 8;
110	info->R[PSC_VOLTAGE_IN] = 2;
111	info->m[PSC_VOLTAGE_OUT] = 8;
112	info->R[PSC_VOLTAGE_OUT] = 2;
113	info->read_word_data = ina233_read_word_data;
114
115	/* If INA233 skips current/power, shunt-resistor and current-lsb aren't needed.	*/
116	/* read rshunt value (uOhm) */
117	ret = device_property_read_u32(dev, "shunt-resistor", &rshunt);
118	if (ret) {
119		if (ret != -EINVAL)
120			return dev_err_probe(dev, ret, "Shunt resistor property read fail.\n");
121		rshunt = INA233_RSHUNT_DEFAULT;
122	}
123	if (!rshunt)
124		return dev_err_probe(dev, -EINVAL,
125				     "Shunt resistor cannot be zero.\n");
126
127	/* read Maximum expected current value (uA) */
128	ret = device_property_read_u32(dev, "ti,maximum-expected-current-microamp", &max_current);
129	if (ret) {
130		if (ret != -EINVAL)
131			return dev_err_probe(dev, ret,
132					     "Maximum expected current property read fail.\n");
133		max_current = INA233_MAX_CURRENT_DEFAULT;
134	}
135	if (max_current < 32768)
136		return dev_err_probe(dev, -EINVAL,
137				     "Maximum expected current cannot less then 32768.\n");
138
139	/* Calculate Current_LSB according to the spec formula */
140	current_lsb = max_current / 32768;
141
142	/* calculate current coefficient */
143	calculate_coef(&m, &R, current_lsb, 1);
144	info->m[PSC_CURRENT_OUT] = m;
145	info->R[PSC_CURRENT_OUT] = R;
146
147	/* calculate power coefficient */
148	calculate_coef(&m, &R, current_lsb, 25);
149	info->m[PSC_POWER] = m;
150	info->R[PSC_POWER] = R;
151
152	/* write MFR_CALIBRATION register, Apply formula from spec with unit scaling. */
153	calibration = div64_u64(5120000000ULL, (u64)rshunt * current_lsb);
154	if (calibration > 0x7FFF)
155		return dev_err_probe(dev, -EINVAL,
156				     "Product of Current_LSB %u and shunt resistor %u too small, MFR_CALIBRATION reg exceeds 0x7FFF.\n",
157				     current_lsb, rshunt);
158	ret = i2c_smbus_write_word_data(client, MFR_CALIBRATION, calibration);
159	if (ret < 0)
160		return dev_err_probe(dev, ret, "Unable to write calibration.\n");
161
162	dev_dbg(dev, "power monitor %s (Rshunt = %u uOhm, Current_LSB = %u uA/bit)\n",
163		client->name, rshunt, current_lsb);
164
165	return pmbus_do_probe(client, info);
166}
167
168static const struct i2c_device_id ina233_id[] = {
169	{"ina233", 0},
170	{}
171};
172MODULE_DEVICE_TABLE(i2c, ina233_id);
173
174static const struct of_device_id __maybe_unused ina233_of_match[] = {
175	{ .compatible = "ti,ina233" },
176	{}
177};
178MODULE_DEVICE_TABLE(of, ina233_of_match);
179
180static struct i2c_driver ina233_driver = {
181	.driver = {
182		   .name = "ina233",
183		   .of_match_table = of_match_ptr(ina233_of_match),
184	},
185	.probe = ina233_probe,
186	.id_table = ina233_id,
187};
188
189module_i2c_driver(ina233_driver);
190
191MODULE_AUTHOR("Leo Yang <leo.yang.sy0@gmail.com>");
192MODULE_DESCRIPTION("PMBus driver for INA233 and compatible chips");
193MODULE_LICENSE("GPL");
194MODULE_IMPORT_NS("PMBUS");
~/ Read-only file preview.