// SPDX-License-Identifier: GPL-2.0+ /* * Hardware monitoring driver for Infineon TDA38640 * * Copyright (c) 2023 9elements GmbH * */ #include #include #include #include #include #include #include "pmbus.h" static const struct regulator_desc __maybe_unused tda38640_reg_desc[] = { PMBUS_REGULATOR_ONE("vout"), }; struct tda38640_data { struct pmbus_driver_info info; u32 en_pin_lvl; }; #define to_tda38640_data(x) container_of(x, struct tda38640_data, info) /* * Map PB_ON_OFF_CONFIG_POLARITY_HIGH to PB_OPERATION_CONTROL_ON. */ static int tda38640_read_byte_data(struct i2c_client *client, int page, int reg) { const struct pmbus_driver_info *info = pmbus_get_driver_info(client); struct tda38640_data *data = to_tda38640_data(info); int ret, on_off_config, enabled; if (reg != PMBUS_OPERATION) return -ENODATA; ret = pmbus_read_byte_data(client, page, reg); if (ret < 0) return ret; on_off_config = pmbus_read_byte_data(client, page, PMBUS_ON_OFF_CONFIG); if (on_off_config < 0) return on_off_config; enabled = !!(on_off_config & PB_ON_OFF_CONFIG_POLARITY_HIGH); enabled ^= data->en_pin_lvl; if (enabled) ret &= ~PB_OPERATION_CONTROL_ON; else ret |= PB_OPERATION_CONTROL_ON; return ret; } /* * Map PB_OPERATION_CONTROL_ON to PB_ON_OFF_CONFIG_POLARITY_HIGH. */ static int tda38640_write_byte_data(struct i2c_client *client, int page, int reg, u8 byte) { const struct pmbus_driver_info *info = pmbus_get_driver_info(client); struct tda38640_data *data = to_tda38640_data(info); int enable, ret; if (reg != PMBUS_OPERATION) return -ENODATA; enable = !!(byte & PB_OPERATION_CONTROL_ON); byte &= ~PB_OPERATION_CONTROL_ON; ret = pmbus_write_byte_data(client, page, reg, byte); if (ret < 0) return ret; enable ^= data->en_pin_lvl; return pmbus_update_byte_data(client, page, PMBUS_ON_OFF_CONFIG, PB_ON_OFF_CONFIG_POLARITY_HIGH, enable ? 0 : PB_ON_OFF_CONFIG_POLARITY_HIGH); } static int svid_mode(struct i2c_client *client, struct tda38640_data *data) { /* PMBUS_MFR_READ(0xD0) + MTP Address offset */ u8 write_buf[] = {0xd0, 0x44, 0x00}; u8 read_buf[2]; int ret, svid; bool off, reg_en_pin_pol; struct i2c_msg msgs[2] = { { .addr = client->addr, .flags = 0, .buf = write_buf, .len = sizeof(write_buf), }, { .addr = client->addr, .flags = I2C_M_RD, .buf = read_buf, .len = sizeof(read_buf), } }; ret = i2c_transfer(client->adapter, msgs, 2); if (ret < 0) { dev_err(&client->dev, "i2c_transfer failed. %d", ret); return ret; } /* * 0x44[15] determines PMBus Operating Mode * If bit is set then it is SVID mode. */ svid = !!(read_buf[1] & BIT(7)); /* * Determine EN pin level for use in SVID mode. * This is done with help of STATUS_BYTE bit 6(OFF) & ON_OFF_CONFIG bit 2(EN pin polarity). */ if (svid) { ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE); if (ret < 0) return ret; off = !!(ret & PB_STATUS_OFF); ret = i2c_smbus_read_byte_data(client, PMBUS_ON_OFF_CONFIG); if (ret < 0) return ret; reg_en_pin_pol = !!(ret & PB_ON_OFF_CONFIG_POLARITY_HIGH); data->en_pin_lvl = off ^ reg_en_pin_pol; } return svid; } static struct pmbus_driver_info tda38640_info = { .pages = 1, .format[PSC_VOLTAGE_IN] = linear, .format[PSC_VOLTAGE_OUT] = linear, .format[PSC_CURRENT_OUT] = linear, .format[PSC_CURRENT_IN] = linear, .format[PSC_POWER] = linear, .format[PSC_TEMPERATURE] = linear, .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_IIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN, #if IS_ENABLED(CONFIG_SENSORS_TDA38640_REGULATOR) .num_regulators = 1, .reg_desc = tda38640_reg_desc, #endif }; static int tda38640_probe(struct i2c_client *client) { struct tda38640_data *data; int svid; data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; memcpy(&data->info, &tda38640_info, sizeof(tda38640_info)); if (IS_ENABLED(CONFIG_SENSORS_TDA38640_REGULATOR) && of_property_read_bool(client->dev.of_node, "infineon,en-pin-fixed-level")) { svid = svid_mode(client, data); if (svid < 0) { dev_err_probe(&client->dev, svid, "Could not determine operating mode."); return svid; } /* * Apply ON_OFF_CONFIG workaround as enabling the regulator using the * OPERATION register doesn't work in SVID mode. * * One should configure PMBUS_ON_OFF_CONFIG here, but * PB_ON_OFF_CONFIG_POWERUP_CONTROL and PB_ON_OFF_CONFIG_EN_PIN_REQ * are ignored by the device. * Only PB_ON_OFF_CONFIG_POLARITY_HIGH has an effect. */ if (svid) { data->info.read_byte_data = tda38640_read_byte_data; data->info.write_byte_data = tda38640_write_byte_data; } } return pmbus_do_probe(client, &data->info); } static const struct i2c_device_id tda38640_id[] = { {"tda38640"}, {} }; MODULE_DEVICE_TABLE(i2c, tda38640_id); static const struct of_device_id __maybe_unused tda38640_of_match[] = { { .compatible = "infineon,tda38640"}, { }, }; MODULE_DEVICE_TABLE(of, tda38640_of_match); /* This is the driver that will be inserted */ static struct i2c_driver tda38640_driver = { .driver = { .name = "tda38640", .of_match_table = of_match_ptr(tda38640_of_match), }, .probe = tda38640_probe, .id_table = tda38640_id, }; module_i2c_driver(tda38640_driver); MODULE_AUTHOR("Patrick Rudolph "); MODULE_DESCRIPTION("PMBus driver for Infineon TDA38640"); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS(PMBUS);