File Index
Files and directories
Sign in
FILE

mp9945.c

File preview with syntax highlighting.

linux-6.19.14/drivers/hwmon/pmbus/mp9945.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Hardware monitoring driver for MPS Single-phase Digital VR Controllers(MP9945)
  4 */
  5
  6#include <linux/bitfield.h>
  7#include <linux/i2c.h>
  8#include <linux/module.h>
  9#include <linux/of_device.h>
 10#include "pmbus.h"
 11
 12#define MFR_VR_MULTI_CONFIG_R1	0x08
 13#define MFR_SVID_CFG_R1		0xBD
 14
 15/* VOUT_MODE register values */
 16#define VOUT_MODE_LINEAR16	0x17
 17#define VOUT_MODE_VID		0x21
 18#define VOUT_MODE_DIRECT	0x40
 19
 20#define MP9945_PAGE_NUM		1
 21
 22#define MP9945_RAIL1_FUNC	(PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
 23				PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | \
 24				PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | \
 25				PMBUS_HAVE_TEMP | \
 26				PMBUS_HAVE_STATUS_VOUT | \
 27				PMBUS_HAVE_STATUS_IOUT | \
 28				PMBUS_HAVE_STATUS_TEMP | \
 29				PMBUS_HAVE_STATUS_INPUT)
 30
 31enum mp9945_vout_mode {
 32	MP9945_VOUT_MODE_VID,
 33	MP9945_VOUT_MODE_DIRECT,
 34	MP9945_VOUT_MODE_LINEAR16,
 35};
 36
 37struct mp9945_data {
 38	struct pmbus_driver_info info;
 39	enum mp9945_vout_mode vout_mode;
 40	int vid_resolution;
 41	int vid_offset;
 42};
 43
 44#define to_mp9945_data(x) container_of(x, struct mp9945_data, info)
 45
 46static int mp9945_read_vout(struct i2c_client *client, struct mp9945_data *data)
 47{
 48	int ret;
 49
 50	ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT);
 51	if (ret < 0)
 52		return ret;
 53
 54	ret &= GENMASK(11, 0);
 55
 56	switch (data->vout_mode) {
 57	case MP9945_VOUT_MODE_VID:
 58		if (ret > 0)
 59			ret = (ret + data->vid_offset) * data->vid_resolution;
 60		break;
 61	case MP9945_VOUT_MODE_DIRECT:
 62		break;
 63	case MP9945_VOUT_MODE_LINEAR16:
 64		/* LSB: 1000 * 2^-9 (mV) */
 65		ret = DIV_ROUND_CLOSEST(ret * 125, 64);
 66		break;
 67	default:
 68		return -ENODEV;
 69	}
 70
 71	return ret;
 72}
 73
 74static int mp9945_read_byte_data(struct i2c_client *client, int page, int reg)
 75{
 76	int ret;
 77
 78	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
 79	if (ret < 0)
 80		return ret;
 81
 82	switch (reg) {
 83	case PMBUS_VOUT_MODE:
 84		/*
 85		 * Override VOUT_MODE to DIRECT as the driver handles custom
 86		 * VOUT format conversions internally.
 87		 */
 88		return PB_VOUT_MODE_DIRECT;
 89	default:
 90		return -ENODATA;
 91	}
 92}
 93
 94static int mp9945_read_word_data(struct i2c_client *client, int page, int phase,
 95				 int reg)
 96{
 97	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 98	struct mp9945_data *data = to_mp9945_data(info);
 99	int ret;
100
101	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
102	if (ret < 0)
103		return ret;
104
105	switch (reg) {
106	case PMBUS_READ_VOUT:
107		ret = mp9945_read_vout(client, data);
108		break;
109	case PMBUS_VOUT_OV_FAULT_LIMIT:
110	case PMBUS_VOUT_UV_FAULT_LIMIT:
111		ret = i2c_smbus_read_word_data(client, reg);
112		if (ret < 0)
113			return ret;
114
115		/* LSB: 1.95 (mV) */
116		ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * 39, 20);
117		break;
118	case PMBUS_VOUT_UV_WARN_LIMIT:
119		ret = i2c_smbus_read_word_data(client, reg);
120		if (ret < 0)
121			return ret;
122
123		ret &= GENMASK(9, 0);
124		if (ret > 0)
125			ret = (ret + data->vid_offset) * data->vid_resolution;
126		break;
127	default:
128		ret = -ENODATA;
129		break;
130	}
131
132	return ret;
133}
134
135static int mp9945_identify(struct i2c_client *client,
136			   struct pmbus_driver_info *info)
137{
138	struct mp9945_data *data = to_mp9945_data(info);
139	int ret;
140
141	ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE);
142	if (ret < 0)
143		return ret;
144
145	switch (ret) {
146	case VOUT_MODE_LINEAR16:
147		data->vout_mode = MP9945_VOUT_MODE_LINEAR16;
148		break;
149	case VOUT_MODE_VID:
150		data->vout_mode = MP9945_VOUT_MODE_VID;
151		break;
152	case VOUT_MODE_DIRECT:
153		data->vout_mode = MP9945_VOUT_MODE_DIRECT;
154		break;
155	default:
156		return -ENODEV;
157	}
158
159	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 3);
160	if (ret < 0)
161		return ret;
162
163	ret = i2c_smbus_read_word_data(client, MFR_VR_MULTI_CONFIG_R1);
164	if (ret < 0)
165		return ret;
166
167	data->vid_resolution = (FIELD_GET(BIT(2), ret)) ? 5 : 10;
168
169	ret = i2c_smbus_read_word_data(client, MFR_SVID_CFG_R1);
170	if (ret < 0)
171		return ret;
172
173	data->vid_offset = (FIELD_GET(BIT(15), ret)) ? 19 : 49;
174
175	return i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
176}
177
178static struct pmbus_driver_info mp9945_info = {
179	.pages = MP9945_PAGE_NUM,
180	.format[PSC_VOLTAGE_IN] = linear,
181	.format[PSC_VOLTAGE_OUT] = direct,
182	.format[PSC_CURRENT_IN] = linear,
183	.format[PSC_CURRENT_OUT] = linear,
184	.format[PSC_POWER] = linear,
185	.format[PSC_TEMPERATURE] = linear,
186	.m[PSC_VOLTAGE_OUT] = 1,
187	.R[PSC_VOLTAGE_OUT] = 3,
188	.b[PSC_VOLTAGE_OUT] = 0,
189	.func[0] = MP9945_RAIL1_FUNC,
190	.read_word_data = mp9945_read_word_data,
191	.read_byte_data = mp9945_read_byte_data,
192	.identify = mp9945_identify,
193};
194
195static int mp9945_probe(struct i2c_client *client)
196{
197	struct mp9945_data *data;
198	int ret;
199
200	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
201	if (!data)
202		return -ENOMEM;
203
204	memcpy(&data->info, &mp9945_info, sizeof(mp9945_info));
205
206	/*
207	 * Set page 0 before probe. The core reads paged registers which are
208	 * only on page 0 for this device.
209	 */
210	ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
211	if (ret < 0)
212		return ret;
213
214	return pmbus_do_probe(client, &data->info);
215}
216
217static const struct i2c_device_id mp9945_id[] = {
218	{"mp9945"},
219	{}
220};
221MODULE_DEVICE_TABLE(i2c, mp9945_id);
222
223static const struct of_device_id __maybe_unused mp9945_of_match[] = {
224	{.compatible = "mps,mp9945"},
225	{}
226};
227MODULE_DEVICE_TABLE(of, mp9945_of_match);
228
229static struct i2c_driver mp9945_driver = {
230	.driver = {
231		.name = "mp9945",
232		.of_match_table = of_match_ptr(mp9945_of_match),
233	},
234	.probe = mp9945_probe,
235	.id_table = mp9945_id,
236};
237
238module_i2c_driver(mp9945_driver);
239
240MODULE_AUTHOR("Cosmo Chou <chou.cosmo@gmail.com>");
241MODULE_DESCRIPTION("PMBus driver for MPS MP9945");
242MODULE_LICENSE("GPL");
243MODULE_IMPORT_NS("PMBUS");
~/ Read-only file preview.