summaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/pmbus/ina233.c
blob: dde1e16783943a51f5c3984ee4f8dca0b05f23ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Hardware monitoring driver for ina233
 *
 * Copyright (c) 2025 Leo Yang
 */

#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "pmbus.h"

#define MFR_READ_VSHUNT 0xd1
#define MFR_CALIBRATION 0xd4

#define INA233_MAX_CURRENT_DEFAULT	32768000 /* uA */
#define INA233_RSHUNT_DEFAULT		2000 /* uOhm */

#define MAX_M_VAL 32767

static void calculate_coef(int *m, int *R, u32 current_lsb, int power_coef)
{
	u64 scaled_m;
	int scale_factor = 0;
	int scale_coef = 1;

	/*
	 * 1000000 from Current_LSB A->uA .
	 * scale_coef is for scaling up to minimize rounding errors,
	 * If there is no decimal information, no need to scale.
	 */
	if (1000000 % current_lsb) {
		/* Scaling to keep integer precision */
		scale_factor = -3;
		scale_coef = 1000;
	}

	/*
	 * Unit Conversion (Current_LSB A->uA) and use scaling(scale_factor)
	 * to keep integer precision.
	 * Formulae referenced from spec.
	 */
	scaled_m = div64_u64(1000000 * scale_coef, (u64)current_lsb * power_coef);

	/* Maximize while keeping it bounded.*/
	while (scaled_m > MAX_M_VAL) {
		scaled_m = div_u64(scaled_m, 10);
		scale_factor++;
	}
	/* Scale up only if fractional part exists. */
	while (scaled_m * 10 < MAX_M_VAL && scale_coef != 1) {
		scaled_m *= 10;
		scale_factor--;
	}

	*m = scaled_m;
	*R = scale_factor;
}

static int ina233_read_word_data(struct i2c_client *client, int page,
				 int phase, int reg)
{
	int ret;

	switch (reg) {
	case PMBUS_VIRT_READ_VMON:
		ret = pmbus_read_word_data(client, 0, 0xff, MFR_READ_VSHUNT);

		/* Adjust returned value to match VIN coefficients */
		/* VIN: 1.25 mV VSHUNT: 2.5 uV LSB */
		ret = DIV_ROUND_CLOSEST(ret * 25, 12500);
		break;
	default:
		ret = -ENODATA;
		break;
	}
	return ret;
}

static int ina233_probe(struct i2c_client *client)
{
	struct device *dev = &client->dev;
	int ret, m, R;
	u32 rshunt;
	u32 max_current;
	u32 current_lsb;
	u16 calibration;
	struct pmbus_driver_info *info;

	info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info),
			    GFP_KERNEL);
	if (!info)
		return -ENOMEM;

	info->pages = 1;
	info->format[PSC_VOLTAGE_IN] = direct;
	info->format[PSC_VOLTAGE_OUT] = direct;
	info->format[PSC_CURRENT_OUT] = direct;
	info->format[PSC_POWER] = direct;
	info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_INPUT
		| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
		| PMBUS_HAVE_POUT
		| PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON;
	info->m[PSC_VOLTAGE_IN] = 8;
	info->R[PSC_VOLTAGE_IN] = 2;
	info->m[PSC_VOLTAGE_OUT] = 8;
	info->R[PSC_VOLTAGE_OUT] = 2;
	info->read_word_data = ina233_read_word_data;

	/* If INA233 skips current/power, shunt-resistor and current-lsb aren't needed.	*/
	/* read rshunt value (uOhm) */
	ret = device_property_read_u32(dev, "shunt-resistor", &rshunt);
	if (ret) {
		if (ret != -EINVAL)
			return dev_err_probe(dev, ret, "Shunt resistor property read fail.\n");
		rshunt = INA233_RSHUNT_DEFAULT;
	}
	if (!rshunt)
		return dev_err_probe(dev, -EINVAL,
				     "Shunt resistor cannot be zero.\n");

	/* read Maximum expected current value (uA) */
	ret = device_property_read_u32(dev, "ti,maximum-expected-current-microamp", &max_current);
	if (ret) {
		if (ret != -EINVAL)
			return dev_err_probe(dev, ret,
					     "Maximum expected current property read fail.\n");
		max_current = INA233_MAX_CURRENT_DEFAULT;
	}
	if (max_current < 32768)
		return dev_err_probe(dev, -EINVAL,
				     "Maximum expected current cannot less then 32768.\n");

	/* Calculate Current_LSB according to the spec formula */
	current_lsb = max_current / 32768;

	/* calculate current coefficient */
	calculate_coef(&m, &R, current_lsb, 1);
	info->m[PSC_CURRENT_OUT] = m;
	info->R[PSC_CURRENT_OUT] = R;

	/* calculate power coefficient */
	calculate_coef(&m, &R, current_lsb, 25);
	info->m[PSC_POWER] = m;
	info->R[PSC_POWER] = R;

	/* write MFR_CALIBRATION register, Apply formula from spec with unit scaling. */
	calibration = div64_u64(5120000000ULL, (u64)rshunt * current_lsb);
	if (calibration > 0x7FFF)
		return dev_err_probe(dev, -EINVAL,
				     "Product of Current_LSB %u and shunt resistor %u too small, MFR_CALIBRATION reg exceeds 0x7FFF.\n",
				     current_lsb, rshunt);
	ret = i2c_smbus_write_word_data(client, MFR_CALIBRATION, calibration);
	if (ret < 0)
		return dev_err_probe(dev, ret, "Unable to write calibration.\n");

	dev_dbg(dev, "power monitor %s (Rshunt = %u uOhm, Current_LSB = %u uA/bit)\n",
		client->name, rshunt, current_lsb);

	return pmbus_do_probe(client, info);
}

static const struct i2c_device_id ina233_id[] = {
	{"ina233", 0},
	{}
};
MODULE_DEVICE_TABLE(i2c, ina233_id);

static const struct of_device_id __maybe_unused ina233_of_match[] = {
	{ .compatible = "ti,ina233" },
	{}
};
MODULE_DEVICE_TABLE(of, ina233_of_match);

static struct i2c_driver ina233_driver = {
	.driver = {
		   .name = "ina233",
		   .of_match_table = of_match_ptr(ina233_of_match),
	},
	.probe = ina233_probe,
	.id_table = ina233_id,
};

module_i2c_driver(ina233_driver);

MODULE_AUTHOR("Leo Yang <leo.yang.sy0@gmail.com>");
MODULE_DESCRIPTION("PMBus driver for INA233 and compatible chips");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");