1*a2981c20SChris Packham // SPDX-License-Identifier: GPL-2.0-or-later 2*a2981c20SChris Packham /* 3*a2981c20SChris Packham * Hardware monitoring driver for Sony APS-379 Power Supplies 4*a2981c20SChris Packham * 5*a2981c20SChris Packham * Copyright 2026 Allied Telesis Labs 6*a2981c20SChris Packham */ 7*a2981c20SChris Packham 8*a2981c20SChris Packham #include <linux/i2c.h> 9*a2981c20SChris Packham #include <linux/init.h> 10*a2981c20SChris Packham #include <linux/kernel.h> 11*a2981c20SChris Packham #include <linux/module.h> 12*a2981c20SChris Packham #include <linux/pmbus.h> 13*a2981c20SChris Packham #include "pmbus.h" 14*a2981c20SChris Packham 15*a2981c20SChris Packham /* 16*a2981c20SChris Packham * The VOUT format used by the chip is linear11, not linear16. Provide a hard 17*a2981c20SChris Packham * coded VOUT_MODE that says VOUT is in linear mode with a fixed exponent of 18*a2981c20SChris Packham * 2^-4. 19*a2981c20SChris Packham */ 20*a2981c20SChris Packham #define APS_379_VOUT_MODE ((u8)(-4 & 0x1f)) 21*a2981c20SChris Packham 22*a2981c20SChris Packham static int aps_379_read_byte_data(struct i2c_client *client, int page, int reg) 23*a2981c20SChris Packham { 24*a2981c20SChris Packham switch (reg) { 25*a2981c20SChris Packham case PMBUS_VOUT_MODE: 26*a2981c20SChris Packham return APS_379_VOUT_MODE; 27*a2981c20SChris Packham default: 28*a2981c20SChris Packham return -ENODATA; 29*a2981c20SChris Packham } 30*a2981c20SChris Packham } 31*a2981c20SChris Packham 32*a2981c20SChris Packham /* 33*a2981c20SChris Packham * The APS-379 uses linear11 format instead of linear16. We've reported the exponent 34*a2981c20SChris Packham * via the PMBUS_VOUT_MODE so we just return the mantissa here. 35*a2981c20SChris Packham */ 36*a2981c20SChris Packham static int aps_379_read_vout(struct i2c_client *client) 37*a2981c20SChris Packham { 38*a2981c20SChris Packham int ret; 39*a2981c20SChris Packham 40*a2981c20SChris Packham ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_READ_VOUT); 41*a2981c20SChris Packham if (ret < 0) 42*a2981c20SChris Packham return ret; 43*a2981c20SChris Packham 44*a2981c20SChris Packham return clamp_val(sign_extend32(ret & 0x7ff, 10), 0, 0x3ff); 45*a2981c20SChris Packham } 46*a2981c20SChris Packham 47*a2981c20SChris Packham static int aps_379_read_word_data(struct i2c_client *client, int page, int phase, int reg) 48*a2981c20SChris Packham { 49*a2981c20SChris Packham switch (reg) { 50*a2981c20SChris Packham case PMBUS_VOUT_UV_WARN_LIMIT: 51*a2981c20SChris Packham case PMBUS_VOUT_OV_WARN_LIMIT: 52*a2981c20SChris Packham case PMBUS_VOUT_UV_FAULT_LIMIT: 53*a2981c20SChris Packham case PMBUS_VOUT_OV_FAULT_LIMIT: 54*a2981c20SChris Packham case PMBUS_IOUT_OC_WARN_LIMIT: 55*a2981c20SChris Packham case PMBUS_IOUT_UC_FAULT_LIMIT: 56*a2981c20SChris Packham case PMBUS_UT_WARN_LIMIT: 57*a2981c20SChris Packham case PMBUS_UT_FAULT_LIMIT: 58*a2981c20SChris Packham case PMBUS_OT_WARN_LIMIT: 59*a2981c20SChris Packham case PMBUS_OT_FAULT_LIMIT: 60*a2981c20SChris Packham case PMBUS_PIN_OP_WARN_LIMIT: 61*a2981c20SChris Packham case PMBUS_POUT_OP_WARN_LIMIT: 62*a2981c20SChris Packham case PMBUS_MFR_IIN_MAX: 63*a2981c20SChris Packham case PMBUS_MFR_PIN_MAX: 64*a2981c20SChris Packham case PMBUS_MFR_VOUT_MIN: 65*a2981c20SChris Packham case PMBUS_MFR_VOUT_MAX: 66*a2981c20SChris Packham case PMBUS_MFR_IOUT_MAX: 67*a2981c20SChris Packham case PMBUS_MFR_POUT_MAX: 68*a2981c20SChris Packham case PMBUS_MFR_MAX_TEMP_1: 69*a2981c20SChris Packham /* These commands return data but it is invalid/un-documented */ 70*a2981c20SChris Packham return -ENXIO; 71*a2981c20SChris Packham case PMBUS_IOUT_OC_FAULT_LIMIT: 72*a2981c20SChris Packham /* 73*a2981c20SChris Packham * The standard requires this to be a value in Amps but it's 74*a2981c20SChris Packham * actually a percentage of the rated output (123A for 75*a2981c20SChris Packham * 110-240Vac, 110A for 90-100Vac) which we don't know. Ignore 76*a2981c20SChris Packham * it rather than guessing. 77*a2981c20SChris Packham */ 78*a2981c20SChris Packham return -ENXIO; 79*a2981c20SChris Packham case PMBUS_READ_VOUT: 80*a2981c20SChris Packham return aps_379_read_vout(client); 81*a2981c20SChris Packham default: 82*a2981c20SChris Packham return -ENODATA; 83*a2981c20SChris Packham } 84*a2981c20SChris Packham } 85*a2981c20SChris Packham 86*a2981c20SChris Packham static struct pmbus_driver_info aps_379_info = { 87*a2981c20SChris Packham .pages = 1, 88*a2981c20SChris Packham .format[PSC_VOLTAGE_OUT] = linear, 89*a2981c20SChris Packham .format[PSC_CURRENT_OUT] = linear, 90*a2981c20SChris Packham .format[PSC_POWER] = linear, 91*a2981c20SChris Packham .format[PSC_TEMPERATURE] = linear, 92*a2981c20SChris Packham .format[PSC_FAN] = linear, 93*a2981c20SChris Packham .func[0] = PMBUS_HAVE_VOUT | 94*a2981c20SChris Packham PMBUS_HAVE_IOUT | 95*a2981c20SChris Packham PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | 96*a2981c20SChris Packham PMBUS_HAVE_TEMP | 97*a2981c20SChris Packham PMBUS_HAVE_FAN12, 98*a2981c20SChris Packham .read_byte_data = aps_379_read_byte_data, 99*a2981c20SChris Packham .read_word_data = aps_379_read_word_data, 100*a2981c20SChris Packham }; 101*a2981c20SChris Packham 102*a2981c20SChris Packham static const struct i2c_device_id aps_379_id[] = { 103*a2981c20SChris Packham { "aps-379", 0 }, 104*a2981c20SChris Packham {}, 105*a2981c20SChris Packham }; 106*a2981c20SChris Packham MODULE_DEVICE_TABLE(i2c, aps_379_id); 107*a2981c20SChris Packham 108*a2981c20SChris Packham static int aps_379_probe(struct i2c_client *client) 109*a2981c20SChris Packham { 110*a2981c20SChris Packham struct device *dev = &client->dev; 111*a2981c20SChris Packham u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = { 0 }; 112*a2981c20SChris Packham int ret; 113*a2981c20SChris Packham 114*a2981c20SChris Packham if (!i2c_check_functionality(client->adapter, 115*a2981c20SChris Packham I2C_FUNC_SMBUS_READ_BYTE_DATA 116*a2981c20SChris Packham | I2C_FUNC_SMBUS_READ_WORD_DATA 117*a2981c20SChris Packham | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) 118*a2981c20SChris Packham return -ENODEV; 119*a2981c20SChris Packham 120*a2981c20SChris Packham ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); 121*a2981c20SChris Packham if (ret < 0) { 122*a2981c20SChris Packham dev_err(dev, "Failed to read Manufacturer Model\n"); 123*a2981c20SChris Packham return ret; 124*a2981c20SChris Packham } 125*a2981c20SChris Packham 126*a2981c20SChris Packham if (strncasecmp(buf, aps_379_id[0].name, strlen(aps_379_id[0].name)) != 0) { 127*a2981c20SChris Packham buf[ret] = '\0'; 128*a2981c20SChris Packham dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); 129*a2981c20SChris Packham return -ENODEV; 130*a2981c20SChris Packham } 131*a2981c20SChris Packham 132*a2981c20SChris Packham return pmbus_do_probe(client, &aps_379_info); 133*a2981c20SChris Packham } 134*a2981c20SChris Packham 135*a2981c20SChris Packham static const struct of_device_id __maybe_unused aps_379_of_match[] = { 136*a2981c20SChris Packham { .compatible = "sony,aps-379" }, 137*a2981c20SChris Packham {}, 138*a2981c20SChris Packham }; 139*a2981c20SChris Packham MODULE_DEVICE_TABLE(of, aps_379_of_match); 140*a2981c20SChris Packham 141*a2981c20SChris Packham static struct i2c_driver aps_379_driver = { 142*a2981c20SChris Packham .driver = { 143*a2981c20SChris Packham .name = "aps-379", 144*a2981c20SChris Packham .of_match_table = of_match_ptr(aps_379_of_match), 145*a2981c20SChris Packham }, 146*a2981c20SChris Packham .probe = aps_379_probe, 147*a2981c20SChris Packham .id_table = aps_379_id, 148*a2981c20SChris Packham }; 149*a2981c20SChris Packham 150*a2981c20SChris Packham module_i2c_driver(aps_379_driver); 151*a2981c20SChris Packham 152*a2981c20SChris Packham MODULE_AUTHOR("Chris Packham"); 153*a2981c20SChris Packham MODULE_DESCRIPTION("PMBus driver for Sony APS-379"); 154*a2981c20SChris Packham MODULE_LICENSE("GPL"); 155*a2981c20SChris Packham MODULE_IMPORT_NS("PMBUS"); 156