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 17 enum chips { pim4006, pim4328, pim4820 }; 18 19 struct 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 41 static 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 }; 52 MODULE_DEVICE_TABLE(i2c, pim4328_id); 53 54 static 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 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 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 83 static 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, PMBUS_STATUS_BYTE); 95 if (ret < 0) 96 return ret; 97 if (data->id == pim4006) { 98 status = pmbus_read_word_data(client, page, 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 133 static 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(client->adapter, 144 I2C_FUNC_SMBUS_READ_BYTE_DATA 145 | I2C_FUNC_SMBUS_BLOCK_DATA)) 146 return -ENODEV; 147 148 data = devm_kzalloc(&client->dev, sizeof(struct pim4328_data), 149 GFP_KERNEL); 150 if (!data) 151 return -ENOMEM; 152 153 status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, 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(mid->name, 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, 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 220 static struct i2c_driver pim4328_driver = { 221 .driver = { 222 .name = "pim4328", 223 }, 224 .probe_new = pim4328_probe, 225 .id_table = pim4328_id, 226 }; 227 228 module_i2c_driver(pim4328_driver); 229 230 MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>"); 231 MODULE_DESCRIPTION("PMBus driver for PIM4006, PIM4328, PIM4820 power interface modules"); 232 MODULE_LICENSE("GPL"); 233 MODULE_IMPORT_NS(PMBUS); 234