1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Hardware monitoring driver for Infineon TDA38640 4 * 5 * Copyright (c) 2023 9elements GmbH 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 <linux/regulator/driver.h> 15 #include "pmbus.h" 16 17 static const struct regulator_desc __maybe_unused tda38640_reg_desc[] = { 18 PMBUS_REGULATOR_ONE("vout"), 19 }; 20 21 struct tda38640_data { 22 struct pmbus_driver_info info; 23 u32 en_pin_lvl; 24 }; 25 26 #define to_tda38640_data(x) container_of(x, struct tda38640_data, info) 27 28 /* 29 * Map PB_ON_OFF_CONFIG_POLARITY_HIGH to PB_OPERATION_CONTROL_ON. 30 */ 31 static int tda38640_read_byte_data(struct i2c_client *client, int page, int reg) 32 { 33 const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 34 struct tda38640_data *data = to_tda38640_data(info); 35 int ret, on_off_config, enabled; 36 37 if (reg != PMBUS_OPERATION) 38 return -ENODATA; 39 40 ret = pmbus_read_byte_data(client, page, reg); 41 if (ret < 0) 42 return ret; 43 44 on_off_config = pmbus_read_byte_data(client, page, 45 PMBUS_ON_OFF_CONFIG); 46 if (on_off_config < 0) 47 return on_off_config; 48 49 enabled = !!(on_off_config & PB_ON_OFF_CONFIG_POLARITY_HIGH); 50 51 enabled ^= data->en_pin_lvl; 52 if (enabled) 53 ret &= ~PB_OPERATION_CONTROL_ON; 54 else 55 ret |= PB_OPERATION_CONTROL_ON; 56 57 return ret; 58 } 59 60 /* 61 * Map PB_OPERATION_CONTROL_ON to PB_ON_OFF_CONFIG_POLARITY_HIGH. 62 */ 63 static int tda38640_write_byte_data(struct i2c_client *client, int page, 64 int reg, u8 byte) 65 { 66 const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 67 struct tda38640_data *data = to_tda38640_data(info); 68 int enable, ret; 69 70 if (reg != PMBUS_OPERATION) 71 return -ENODATA; 72 73 enable = !!(byte & PB_OPERATION_CONTROL_ON); 74 75 byte &= ~PB_OPERATION_CONTROL_ON; 76 ret = pmbus_write_byte_data(client, page, reg, byte); 77 if (ret < 0) 78 return ret; 79 80 enable ^= data->en_pin_lvl; 81 82 return pmbus_update_byte_data(client, page, PMBUS_ON_OFF_CONFIG, 83 PB_ON_OFF_CONFIG_POLARITY_HIGH, 84 enable ? 0 : PB_ON_OFF_CONFIG_POLARITY_HIGH); 85 } 86 87 static int svid_mode(struct i2c_client *client, struct tda38640_data *data) 88 { 89 /* PMBUS_MFR_READ(0xD0) + MTP Address offset */ 90 u8 write_buf[] = {0xd0, 0x44, 0x00}; 91 u8 read_buf[2]; 92 int ret, svid; 93 bool off, reg_en_pin_pol; 94 95 struct i2c_msg msgs[2] = { 96 { 97 .addr = client->addr, 98 .flags = 0, 99 .buf = write_buf, 100 .len = sizeof(write_buf), 101 }, 102 { 103 .addr = client->addr, 104 .flags = I2C_M_RD, 105 .buf = read_buf, 106 .len = sizeof(read_buf), 107 } 108 }; 109 110 ret = i2c_transfer(client->adapter, msgs, 2); 111 if (ret < 0) { 112 dev_err(&client->dev, "i2c_transfer failed. %d", ret); 113 return ret; 114 } 115 116 /* 117 * 0x44[15] determines PMBus Operating Mode 118 * If bit is set then it is SVID mode. 119 */ 120 svid = !!(read_buf[1] & BIT(7)); 121 122 /* 123 * Determine EN pin level for use in SVID mode. 124 * This is done with help of STATUS_BYTE bit 6(OFF) & ON_OFF_CONFIG bit 2(EN pin polarity). 125 */ 126 if (svid) { 127 ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE); 128 if (ret < 0) 129 return ret; 130 off = !!(ret & PB_STATUS_OFF); 131 132 ret = i2c_smbus_read_byte_data(client, PMBUS_ON_OFF_CONFIG); 133 if (ret < 0) 134 return ret; 135 reg_en_pin_pol = !!(ret & PB_ON_OFF_CONFIG_POLARITY_HIGH); 136 data->en_pin_lvl = off ^ reg_en_pin_pol; 137 } 138 139 return svid; 140 } 141 142 static struct pmbus_driver_info tda38640_info = { 143 .pages = 1, 144 .format[PSC_VOLTAGE_IN] = linear, 145 .format[PSC_VOLTAGE_OUT] = linear, 146 .format[PSC_CURRENT_OUT] = linear, 147 .format[PSC_CURRENT_IN] = linear, 148 .format[PSC_POWER] = linear, 149 .format[PSC_TEMPERATURE] = linear, 150 .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT 151 | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP 152 | PMBUS_HAVE_IIN 153 | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT 154 | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT 155 | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN, 156 #if IS_ENABLED(CONFIG_SENSORS_TDA38640_REGULATOR) 157 .num_regulators = 1, 158 .reg_desc = tda38640_reg_desc, 159 #endif 160 }; 161 162 static int tda38640_probe(struct i2c_client *client) 163 { 164 struct tda38640_data *data; 165 int svid; 166 167 data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); 168 if (!data) 169 return -ENOMEM; 170 memcpy(&data->info, &tda38640_info, sizeof(tda38640_info)); 171 172 if (IS_ENABLED(CONFIG_SENSORS_TDA38640_REGULATOR) && 173 of_property_read_bool(client->dev.of_node, "infineon,en-pin-fixed-level")) { 174 svid = svid_mode(client, data); 175 if (svid < 0) { 176 dev_err_probe(&client->dev, svid, "Could not determine operating mode."); 177 return svid; 178 } 179 180 /* 181 * Apply ON_OFF_CONFIG workaround as enabling the regulator using the 182 * OPERATION register doesn't work in SVID mode. 183 * 184 * One should configure PMBUS_ON_OFF_CONFIG here, but 185 * PB_ON_OFF_CONFIG_POWERUP_CONTROL and PB_ON_OFF_CONFIG_EN_PIN_REQ 186 * are ignored by the device. 187 * Only PB_ON_OFF_CONFIG_POLARITY_HIGH has an effect. 188 */ 189 if (svid) { 190 data->info.read_byte_data = tda38640_read_byte_data; 191 data->info.write_byte_data = tda38640_write_byte_data; 192 } 193 } 194 return pmbus_do_probe(client, &data->info); 195 } 196 197 static const struct i2c_device_id tda38640_id[] = { 198 {"tda38640", 0}, 199 {} 200 }; 201 MODULE_DEVICE_TABLE(i2c, tda38640_id); 202 203 static const struct of_device_id __maybe_unused tda38640_of_match[] = { 204 { .compatible = "infineon,tda38640"}, 205 { }, 206 }; 207 MODULE_DEVICE_TABLE(of, tda38640_of_match); 208 209 /* This is the driver that will be inserted */ 210 static struct i2c_driver tda38640_driver = { 211 .driver = { 212 .name = "tda38640", 213 .of_match_table = of_match_ptr(tda38640_of_match), 214 }, 215 .probe = tda38640_probe, 216 .id_table = tda38640_id, 217 }; 218 219 module_i2c_driver(tda38640_driver); 220 221 MODULE_AUTHOR("Patrick Rudolph <patrick.rudolph@9elements.com>"); 222 MODULE_DESCRIPTION("PMBus driver for Infineon TDA38640"); 223 MODULE_LICENSE("GPL"); 224 MODULE_IMPORT_NS(PMBUS); 225