1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Hardware monitoring driver for Infineon Multi-phase Digital VR Controllers
4 *
5 * Copyright (c) 2020 Mellanox Technologies. All rights reserved.
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 XDPE122_PROT_VR12_5MV		0x01 /* VR12.0 mode, 5-mV DAC */
16#define XDPE122_PROT_VR12_5_10MV	0x02 /* VR12.5 mode, 10-mV DAC */
17#define XDPE122_PROT_IMVP9_10MV		0x03 /* IMVP9 mode, 10-mV DAC */
18#define XDPE122_AMD_625MV		0x10 /* AMD mode 6.25mV */
19#define XDPE122_PAGE_NUM		2
20
21static int xdpe122_read_word_data(struct i2c_client *client, int page,
22				  int phase, int reg)
23{
24	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
25	long val;
26	s16 exponent;
27	s32 mantissa;
28	int ret;
29
30	switch (reg) {
31	case PMBUS_VOUT_OV_FAULT_LIMIT:
32	case PMBUS_VOUT_UV_FAULT_LIMIT:
33		ret = pmbus_read_word_data(client, page, phase, reg);
34		if (ret < 0)
35			return ret;
36
37		/* Convert register value to LINEAR11 data. */
38		exponent = ((s16)ret) >> 11;
39		mantissa = ((s16)((ret & GENMASK(10, 0)) << 5)) >> 5;
40		val = mantissa * 1000L;
41		if (exponent >= 0)
42			val <<= exponent;
43		else
44			val >>= -exponent;
45
46		/* Convert data to VID register. */
47		switch (info->vrm_version[page]) {
48		case vr13:
49			if (val >= 500)
50				return 1 + DIV_ROUND_CLOSEST(val - 500, 10);
51			return 0;
52		case vr12:
53			if (val >= 250)
54				return 1 + DIV_ROUND_CLOSEST(val - 250, 5);
55			return 0;
56		case imvp9:
57			if (val >= 200)
58				return 1 + DIV_ROUND_CLOSEST(val - 200, 10);
59			return 0;
60		case amd625mv:
61			if (val >= 200 && val <= 1550)
62				return DIV_ROUND_CLOSEST((1550 - val) * 100,
63							 625);
64			return 0;
65		default:
66			return -EINVAL;
67		}
68	default:
69		return -ENODATA;
70	}
71
72	return 0;
73}
74
75static int xdpe122_identify(struct i2c_client *client,
76			    struct pmbus_driver_info *info)
77{
78	u8 vout_params;
79	int i, ret;
80
81	for (i = 0; i < XDPE122_PAGE_NUM; i++) {
82		/* Read the register with VOUT scaling value.*/
83		ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE);
84		if (ret < 0)
85			return ret;
86
87		vout_params = ret & GENMASK(4, 0);
88
89		switch (vout_params) {
90		case XDPE122_PROT_VR12_5_10MV:
91			info->vrm_version[i] = vr13;
92			break;
93		case XDPE122_PROT_VR12_5MV:
94			info->vrm_version[i] = vr12;
95			break;
96		case XDPE122_PROT_IMVP9_10MV:
97			info->vrm_version[i] = imvp9;
98			break;
99		case XDPE122_AMD_625MV:
100			info->vrm_version[i] = amd625mv;
101			break;
102		default:
103			return -EINVAL;
104		}
105	}
106
107	return 0;
108}
109
110static struct pmbus_driver_info xdpe122_info = {
111	.pages = XDPE122_PAGE_NUM,
112	.format[PSC_VOLTAGE_IN] = linear,
113	.format[PSC_VOLTAGE_OUT] = vid,
114	.format[PSC_TEMPERATURE] = linear,
115	.format[PSC_CURRENT_IN] = linear,
116	.format[PSC_CURRENT_OUT] = linear,
117	.format[PSC_POWER] = linear,
118	.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
119		PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
120		PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
121		PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
122	.func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
123		PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
124		PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
125		PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
126	.identify = xdpe122_identify,
127	.read_word_data = xdpe122_read_word_data,
128};
129
130static int xdpe122_probe(struct i2c_client *client)
131{
132	struct pmbus_driver_info *info;
133
134	info = devm_kmemdup(&client->dev, &xdpe122_info, sizeof(*info),
135			    GFP_KERNEL);
136	if (!info)
137		return -ENOMEM;
138
139	return pmbus_do_probe(client, info);
140}
141
142static const struct i2c_device_id xdpe122_id[] = {
143	{"xdpe12254", 0},
144	{"xdpe12284", 0},
145	{}
146};
147
148MODULE_DEVICE_TABLE(i2c, xdpe122_id);
149
150static const struct of_device_id __maybe_unused xdpe122_of_match[] = {
151	{.compatible = "infineon,xdpe12254"},
152	{.compatible = "infineon,xdpe12284"},
153	{}
154};
155MODULE_DEVICE_TABLE(of, xdpe122_of_match);
156
157static struct i2c_driver xdpe122_driver = {
158	.driver = {
159		.name = "xdpe12284",
160		.of_match_table = of_match_ptr(xdpe122_of_match),
161	},
162	.probe_new = xdpe122_probe,
163	.remove = pmbus_do_remove,
164	.id_table = xdpe122_id,
165};
166
167module_i2c_driver(xdpe122_driver);
168
169MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
170MODULE_DESCRIPTION("PMBus driver for Infineon XDPE122 family");
171MODULE_LICENSE("GPL");
172