1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Hardware monitoring driver for Infineon PXE1610
4 *
5 * Copyright (c) 2019 Facebook Inc
6 *
7 */
8
9#include <linux/err.h>
10#include <linux/i2c.h>
11#include <linux/init.h>
12#include <linux/kernel.h>
13#include <linux/module.h>
14#include "pmbus.h"
15
16#define PXE1610_NUM_PAGES 3
17
18/* Identify chip parameters. */
19static int pxe1610_identify(struct i2c_client *client,
20			     struct pmbus_driver_info *info)
21{
22	int i;
23
24	for (i = 0; i < PXE1610_NUM_PAGES; i++) {
25		if (pmbus_check_byte_register(client, i, PMBUS_VOUT_MODE)) {
26			u8 vout_mode;
27			int ret;
28
29			/* Read the register with VOUT scaling value.*/
30			ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE);
31			if (ret < 0)
32				return ret;
33
34			vout_mode = ret & GENMASK(4, 0);
35
36			switch (vout_mode) {
37			case 1:
38				info->vrm_version[i] = vr12;
39				break;
40			case 2:
41				info->vrm_version[i] = vr13;
42				break;
43			default:
44				/*
45				 * If prior pages are available limit operation
46				 * to them
47				 */
48				if (i != 0) {
49					info->pages = i;
50					return 0;
51				}
52
53				return -ENODEV;
54			}
55		}
56	}
57
58	return 0;
59}
60
61static struct pmbus_driver_info pxe1610_info = {
62	.pages = PXE1610_NUM_PAGES,
63	.format[PSC_VOLTAGE_IN] = linear,
64	.format[PSC_VOLTAGE_OUT] = vid,
65	.format[PSC_CURRENT_IN] = linear,
66	.format[PSC_CURRENT_OUT] = linear,
67	.format[PSC_TEMPERATURE] = linear,
68	.format[PSC_POWER] = linear,
69	.func[0] = PMBUS_HAVE_VIN
70		| PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
71		| PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
72		| PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
73		| PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
74		| PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
75	.func[1] = PMBUS_HAVE_VIN
76		| PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
77		| PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
78		| PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
79		| PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
80		| PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
81	.func[2] = PMBUS_HAVE_VIN
82		| PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
83		| PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
84		| PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
85		| PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
86		| PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
87	.identify = pxe1610_identify,
88};
89
90static int pxe1610_probe(struct i2c_client *client)
91{
92	struct pmbus_driver_info *info;
93	u8 buf[I2C_SMBUS_BLOCK_MAX];
94	int ret;
95
96	if (!i2c_check_functionality(
97			client->adapter,
98			I2C_FUNC_SMBUS_READ_BYTE_DATA
99			| I2C_FUNC_SMBUS_READ_WORD_DATA
100			| I2C_FUNC_SMBUS_READ_BLOCK_DATA))
101		return -ENODEV;
102
103	/*
104	 * By default this device doesn't boot to page 0, so set page 0
105	 * to access all pmbus registers.
106	 */
107	i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
108
109	/* Read Manufacturer id */
110	ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
111	if (ret < 0) {
112		dev_err(&client->dev, "Failed to read PMBUS_MFR_ID\n");
113		return ret;
114	}
115	if (ret != 2 || strncmp(buf, "XP", 2)) {
116		dev_err(&client->dev, "MFR_ID unrecognized\n");
117		return -ENODEV;
118	}
119
120	info = devm_kmemdup(&client->dev, &pxe1610_info,
121			    sizeof(struct pmbus_driver_info),
122			    GFP_KERNEL);
123	if (!info)
124		return -ENOMEM;
125
126	return pmbus_do_probe(client, info);
127}
128
129static const struct i2c_device_id pxe1610_id[] = {
130	{"pxe1610", 0},
131	{"pxe1110", 0},
132	{"pxm1310", 0},
133	{}
134};
135
136MODULE_DEVICE_TABLE(i2c, pxe1610_id);
137
138static struct i2c_driver pxe1610_driver = {
139	.driver = {
140			.name = "pxe1610",
141			},
142	.probe = pxe1610_probe,
143	.id_table = pxe1610_id,
144};
145
146module_i2c_driver(pxe1610_driver);
147
148MODULE_AUTHOR("Vijay Khemka <vijaykhemka@fb.com>");
149MODULE_DESCRIPTION("PMBus driver for Infineon PXE1610, PXE1110 and PXM1310");
150MODULE_LICENSE("GPL");
151MODULE_IMPORT_NS(PMBUS);
152