1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Hardware monitoring driver for PIM4006, PIM4328 and PIM4820
4 *
5 * Copyright (c) 2021 Flextronics International Sweden AB
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 <linux/pmbus.h>
14#include <linux/slab.h>
15#include "pmbus.h"
16
17enum chips { pim4006, pim4328, pim4820 };
18
19struct pim4328_data {
20 enum chips id;
21 struct pmbus_driver_info info;
22};
23
24#define to_pim4328_data(x) container_of(x, struct pim4328_data, info)
25
26/* PIM4006 and PIM4328 */
27#define PIM4328_MFR_READ_VINA 0xd3
28#define PIM4328_MFR_READ_VINB 0xd4
29
30/* PIM4006 */
31#define PIM4328_MFR_READ_IINA 0xd6
32#define PIM4328_MFR_READ_IINB 0xd7
33#define PIM4328_MFR_FET_CHECKSTATUS 0xd9
34
35/* PIM4328 */
36#define PIM4328_MFR_STATUS_BITS 0xd5
37
38/* PIM4820 */
39#define PIM4328_MFR_READ_STATUS 0xd0
40
41static const struct i2c_device_id pim4328_id[] = {
42 {"bmr455", pim4328},
43 {"pim4006", pim4006},
44 {"pim4106", pim4006},
45 {"pim4206", pim4006},
46 {"pim4306", pim4006},
47 {"pim4328", pim4328},
48 {"pim4406", pim4006},
49 {"pim4820", pim4820},
50 {}
51};
52MODULE_DEVICE_TABLE(i2c, pim4328_id);
53
54static int pim4328_read_word_data(struct i2c_client *client, int page,
55 int phase, int reg)
56{
57 int ret;
58
59 if (page > 0)
60 return -ENXIO;
61
62 if (phase == 0xff)
63 return -ENODATA;
64
65 switch (reg) {
66 case PMBUS_READ_VIN:
67 ret = pmbus_read_word_data(client, page, phase,
68 reg: phase == 0 ? PIM4328_MFR_READ_VINA
69 : PIM4328_MFR_READ_VINB);
70 break;
71 case PMBUS_READ_IIN:
72 ret = pmbus_read_word_data(client, page, phase,
73 reg: phase == 0 ? PIM4328_MFR_READ_IINA
74 : PIM4328_MFR_READ_IINB);
75 break;
76 default:
77 ret = -ENODATA;
78 }
79
80 return ret;
81}
82
83static int pim4328_read_byte_data(struct i2c_client *client, int page, int reg)
84{
85 const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
86 struct pim4328_data *data = to_pim4328_data(info);
87 int ret, status;
88
89 if (page > 0)
90 return -ENXIO;
91
92 switch (reg) {
93 case PMBUS_STATUS_BYTE:
94 ret = pmbus_read_byte_data(client, page, reg: PMBUS_STATUS_BYTE);
95 if (ret < 0)
96 return ret;
97 if (data->id == pim4006) {
98 status = pmbus_read_word_data(client, page, phase: 0xff,
99 PIM4328_MFR_FET_CHECKSTATUS);
100 if (status < 0)
101 return status;
102 if (status & 0x0630) /* Input UV */
103 ret |= PB_STATUS_VIN_UV;
104 } else if (data->id == pim4328) {
105 status = pmbus_read_byte_data(client, page,
106 PIM4328_MFR_STATUS_BITS);
107 if (status < 0)
108 return status;
109 if (status & 0x04) /* Input UV */
110 ret |= PB_STATUS_VIN_UV;
111 if (status & 0x40) /* Output UV */
112 ret |= PB_STATUS_NONE_ABOVE;
113 } else if (data->id == pim4820) {
114 status = pmbus_read_byte_data(client, page,
115 PIM4328_MFR_READ_STATUS);
116 if (status < 0)
117 return status;
118 if (status & 0x05) /* Input OV or OC */
119 ret |= PB_STATUS_NONE_ABOVE;
120 if (status & 0x1a) /* Input UV */
121 ret |= PB_STATUS_VIN_UV;
122 if (status & 0x40) /* OT */
123 ret |= PB_STATUS_TEMPERATURE;
124 }
125 break;
126 default:
127 ret = -ENODATA;
128 }
129
130 return ret;
131}
132
133static int pim4328_probe(struct i2c_client *client)
134{
135 int status;
136 u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
137 const struct i2c_device_id *mid;
138 struct pim4328_data *data;
139 struct pmbus_driver_info *info;
140 struct pmbus_platform_data *pdata;
141 struct device *dev = &client->dev;
142
143 if (!i2c_check_functionality(adap: client->adapter,
144 I2C_FUNC_SMBUS_READ_BYTE_DATA
145 | I2C_FUNC_SMBUS_BLOCK_DATA))
146 return -ENODEV;
147
148 data = devm_kzalloc(dev: &client->dev, size: sizeof(struct pim4328_data),
149 GFP_KERNEL);
150 if (!data)
151 return -ENOMEM;
152
153 status = i2c_smbus_read_block_data(client, command: PMBUS_MFR_MODEL, values: device_id);
154 if (status < 0) {
155 dev_err(&client->dev, "Failed to read Manufacturer Model\n");
156 return status;
157 }
158 for (mid = pim4328_id; mid->name[0]; mid++) {
159 if (!strncasecmp(s1: mid->name, s2: device_id, strlen(mid->name)))
160 break;
161 }
162 if (!mid->name[0]) {
163 dev_err(&client->dev, "Unsupported device\n");
164 return -ENODEV;
165 }
166
167 if (strcmp(client->name, mid->name))
168 dev_notice(&client->dev,
169 "Device mismatch: Configured %s, detected %s\n",
170 client->name, mid->name);
171
172 data->id = mid->driver_data;
173 info = &data->info;
174 info->pages = 1;
175 info->read_byte_data = pim4328_read_byte_data;
176 info->read_word_data = pim4328_read_word_data;
177
178 pdata = devm_kzalloc(dev, size: sizeof(struct pmbus_platform_data),
179 GFP_KERNEL);
180 if (!pdata)
181 return -ENOMEM;
182 dev->platform_data = pdata;
183 pdata->flags = PMBUS_NO_CAPABILITY | PMBUS_NO_WRITE_PROTECT;
184
185 switch (data->id) {
186 case pim4006:
187 info->phases[0] = 2;
188 info->func[0] = PMBUS_PHASE_VIRTUAL | PMBUS_HAVE_VIN
189 | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
190 info->pfunc[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
191 info->pfunc[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
192 break;
193 case pim4328:
194 info->phases[0] = 2;
195 info->func[0] = PMBUS_PHASE_VIRTUAL
196 | PMBUS_HAVE_VCAP | PMBUS_HAVE_VIN
197 | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
198 info->pfunc[0] = PMBUS_HAVE_VIN;
199 info->pfunc[1] = PMBUS_HAVE_VIN;
200 info->format[PSC_VOLTAGE_IN] = direct;
201 info->format[PSC_TEMPERATURE] = direct;
202 info->format[PSC_CURRENT_OUT] = direct;
203 pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
204 break;
205 case pim4820:
206 info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_TEMP
207 | PMBUS_HAVE_IIN;
208 info->format[PSC_VOLTAGE_IN] = direct;
209 info->format[PSC_TEMPERATURE] = direct;
210 info->format[PSC_CURRENT_IN] = direct;
211 pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
212 break;
213 default:
214 return -ENODEV;
215 }
216
217 return pmbus_do_probe(client, info);
218}
219
220static struct i2c_driver pim4328_driver = {
221 .driver = {
222 .name = "pim4328",
223 },
224 .probe = pim4328_probe,
225 .id_table = pim4328_id,
226};
227
228module_i2c_driver(pim4328_driver);
229
230MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
231MODULE_DESCRIPTION("PMBus driver for PIM4006, PIM4328, PIM4820 power interface modules");
232MODULE_LICENSE("GPL");
233MODULE_IMPORT_NS(PMBUS);
234

source code of linux/drivers/hwmon/pmbus/pim4328.c