1*038a9c3dSMaxim Sloyko // SPDX-License-Identifier: GPL-2.0+ 2*038a9c3dSMaxim Sloyko /* 3*038a9c3dSMaxim Sloyko * Hardware monitoring driver for Intersil ISL68137 4*038a9c3dSMaxim Sloyko * 5*038a9c3dSMaxim Sloyko * Copyright (c) 2017 Google Inc 6*038a9c3dSMaxim Sloyko * 7*038a9c3dSMaxim Sloyko */ 8*038a9c3dSMaxim Sloyko 9*038a9c3dSMaxim Sloyko #include <linux/err.h> 10*038a9c3dSMaxim Sloyko #include <linux/hwmon-sysfs.h> 11*038a9c3dSMaxim Sloyko #include <linux/i2c.h> 12*038a9c3dSMaxim Sloyko #include <linux/init.h> 13*038a9c3dSMaxim Sloyko #include <linux/kernel.h> 14*038a9c3dSMaxim Sloyko #include <linux/module.h> 15*038a9c3dSMaxim Sloyko #include <linux/string.h> 16*038a9c3dSMaxim Sloyko #include <linux/sysfs.h> 17*038a9c3dSMaxim Sloyko #include "pmbus.h" 18*038a9c3dSMaxim Sloyko 19*038a9c3dSMaxim Sloyko #define ISL68137_VOUT_AVS 0x30 20*038a9c3dSMaxim Sloyko 21*038a9c3dSMaxim Sloyko static ssize_t isl68137_avs_enable_show_page(struct i2c_client *client, 22*038a9c3dSMaxim Sloyko int page, 23*038a9c3dSMaxim Sloyko char *buf) 24*038a9c3dSMaxim Sloyko { 25*038a9c3dSMaxim Sloyko int val = pmbus_read_byte_data(client, page, PMBUS_OPERATION); 26*038a9c3dSMaxim Sloyko 27*038a9c3dSMaxim Sloyko return sprintf(buf, "%d\n", 28*038a9c3dSMaxim Sloyko (val & ISL68137_VOUT_AVS) == ISL68137_VOUT_AVS ? 1 : 0); 29*038a9c3dSMaxim Sloyko } 30*038a9c3dSMaxim Sloyko 31*038a9c3dSMaxim Sloyko static ssize_t isl68137_avs_enable_store_page(struct i2c_client *client, 32*038a9c3dSMaxim Sloyko int page, 33*038a9c3dSMaxim Sloyko const char *buf, size_t count) 34*038a9c3dSMaxim Sloyko { 35*038a9c3dSMaxim Sloyko int rc, op_val; 36*038a9c3dSMaxim Sloyko bool result; 37*038a9c3dSMaxim Sloyko 38*038a9c3dSMaxim Sloyko rc = kstrtobool(buf, &result); 39*038a9c3dSMaxim Sloyko if (rc) 40*038a9c3dSMaxim Sloyko return rc; 41*038a9c3dSMaxim Sloyko 42*038a9c3dSMaxim Sloyko op_val = result ? ISL68137_VOUT_AVS : 0; 43*038a9c3dSMaxim Sloyko 44*038a9c3dSMaxim Sloyko /* 45*038a9c3dSMaxim Sloyko * Writes to VOUT setpoint over AVSBus will persist after the VRM is 46*038a9c3dSMaxim Sloyko * switched to PMBus control. Switching back to AVSBus control 47*038a9c3dSMaxim Sloyko * restores this persisted setpoint rather than re-initializing to 48*038a9c3dSMaxim Sloyko * PMBus VOUT_COMMAND. Writing VOUT_COMMAND first over PMBus before 49*038a9c3dSMaxim Sloyko * enabling AVS control is the workaround. 50*038a9c3dSMaxim Sloyko */ 51*038a9c3dSMaxim Sloyko if (op_val == ISL68137_VOUT_AVS) { 52*038a9c3dSMaxim Sloyko rc = pmbus_read_word_data(client, page, PMBUS_VOUT_COMMAND); 53*038a9c3dSMaxim Sloyko if (rc < 0) 54*038a9c3dSMaxim Sloyko return rc; 55*038a9c3dSMaxim Sloyko 56*038a9c3dSMaxim Sloyko rc = pmbus_write_word_data(client, page, PMBUS_VOUT_COMMAND, 57*038a9c3dSMaxim Sloyko rc); 58*038a9c3dSMaxim Sloyko if (rc < 0) 59*038a9c3dSMaxim Sloyko return rc; 60*038a9c3dSMaxim Sloyko } 61*038a9c3dSMaxim Sloyko 62*038a9c3dSMaxim Sloyko rc = pmbus_update_byte_data(client, page, PMBUS_OPERATION, 63*038a9c3dSMaxim Sloyko ISL68137_VOUT_AVS, op_val); 64*038a9c3dSMaxim Sloyko 65*038a9c3dSMaxim Sloyko return (rc < 0) ? rc : count; 66*038a9c3dSMaxim Sloyko } 67*038a9c3dSMaxim Sloyko 68*038a9c3dSMaxim Sloyko static ssize_t isl68137_avs_enable_show(struct device *dev, 69*038a9c3dSMaxim Sloyko struct device_attribute *devattr, 70*038a9c3dSMaxim Sloyko char *buf) 71*038a9c3dSMaxim Sloyko { 72*038a9c3dSMaxim Sloyko struct i2c_client *client = to_i2c_client(dev->parent); 73*038a9c3dSMaxim Sloyko struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 74*038a9c3dSMaxim Sloyko 75*038a9c3dSMaxim Sloyko return isl68137_avs_enable_show_page(client, attr->index, buf); 76*038a9c3dSMaxim Sloyko } 77*038a9c3dSMaxim Sloyko 78*038a9c3dSMaxim Sloyko static ssize_t isl68137_avs_enable_store(struct device *dev, 79*038a9c3dSMaxim Sloyko struct device_attribute *devattr, 80*038a9c3dSMaxim Sloyko const char *buf, size_t count) 81*038a9c3dSMaxim Sloyko { 82*038a9c3dSMaxim Sloyko struct i2c_client *client = to_i2c_client(dev->parent); 83*038a9c3dSMaxim Sloyko struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 84*038a9c3dSMaxim Sloyko 85*038a9c3dSMaxim Sloyko return isl68137_avs_enable_store_page(client, attr->index, buf, count); 86*038a9c3dSMaxim Sloyko } 87*038a9c3dSMaxim Sloyko 88*038a9c3dSMaxim Sloyko static SENSOR_DEVICE_ATTR_RW(avs0_enable, isl68137_avs_enable, 0); 89*038a9c3dSMaxim Sloyko static SENSOR_DEVICE_ATTR_RW(avs1_enable, isl68137_avs_enable, 1); 90*038a9c3dSMaxim Sloyko 91*038a9c3dSMaxim Sloyko static struct attribute *enable_attrs[] = { 92*038a9c3dSMaxim Sloyko &sensor_dev_attr_avs0_enable.dev_attr.attr, 93*038a9c3dSMaxim Sloyko &sensor_dev_attr_avs1_enable.dev_attr.attr, 94*038a9c3dSMaxim Sloyko NULL, 95*038a9c3dSMaxim Sloyko }; 96*038a9c3dSMaxim Sloyko 97*038a9c3dSMaxim Sloyko static const struct attribute_group enable_group = { 98*038a9c3dSMaxim Sloyko .attrs = enable_attrs, 99*038a9c3dSMaxim Sloyko }; 100*038a9c3dSMaxim Sloyko 101*038a9c3dSMaxim Sloyko static const struct attribute_group *attribute_groups[] = { 102*038a9c3dSMaxim Sloyko &enable_group, 103*038a9c3dSMaxim Sloyko NULL, 104*038a9c3dSMaxim Sloyko }; 105*038a9c3dSMaxim Sloyko 106*038a9c3dSMaxim Sloyko static struct pmbus_driver_info isl68137_info = { 107*038a9c3dSMaxim Sloyko .pages = 2, 108*038a9c3dSMaxim Sloyko .format[PSC_VOLTAGE_IN] = direct, 109*038a9c3dSMaxim Sloyko .format[PSC_VOLTAGE_OUT] = direct, 110*038a9c3dSMaxim Sloyko .format[PSC_CURRENT_IN] = direct, 111*038a9c3dSMaxim Sloyko .format[PSC_CURRENT_OUT] = direct, 112*038a9c3dSMaxim Sloyko .format[PSC_POWER] = direct, 113*038a9c3dSMaxim Sloyko .format[PSC_TEMPERATURE] = direct, 114*038a9c3dSMaxim Sloyko .m[PSC_VOLTAGE_IN] = 1, 115*038a9c3dSMaxim Sloyko .b[PSC_VOLTAGE_IN] = 0, 116*038a9c3dSMaxim Sloyko .R[PSC_VOLTAGE_IN] = 3, 117*038a9c3dSMaxim Sloyko .m[PSC_VOLTAGE_OUT] = 1, 118*038a9c3dSMaxim Sloyko .b[PSC_VOLTAGE_OUT] = 0, 119*038a9c3dSMaxim Sloyko .R[PSC_VOLTAGE_OUT] = 3, 120*038a9c3dSMaxim Sloyko .m[PSC_CURRENT_IN] = 1, 121*038a9c3dSMaxim Sloyko .b[PSC_CURRENT_IN] = 0, 122*038a9c3dSMaxim Sloyko .R[PSC_CURRENT_IN] = 2, 123*038a9c3dSMaxim Sloyko .m[PSC_CURRENT_OUT] = 1, 124*038a9c3dSMaxim Sloyko .b[PSC_CURRENT_OUT] = 0, 125*038a9c3dSMaxim Sloyko .R[PSC_CURRENT_OUT] = 1, 126*038a9c3dSMaxim Sloyko .m[PSC_POWER] = 1, 127*038a9c3dSMaxim Sloyko .b[PSC_POWER] = 0, 128*038a9c3dSMaxim Sloyko .R[PSC_POWER] = 0, 129*038a9c3dSMaxim Sloyko .m[PSC_TEMPERATURE] = 1, 130*038a9c3dSMaxim Sloyko .b[PSC_TEMPERATURE] = 0, 131*038a9c3dSMaxim Sloyko .R[PSC_TEMPERATURE] = 0, 132*038a9c3dSMaxim Sloyko .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN 133*038a9c3dSMaxim Sloyko | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 134*038a9c3dSMaxim Sloyko | PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_TEMP 135*038a9c3dSMaxim Sloyko | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT 136*038a9c3dSMaxim Sloyko | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, 137*038a9c3dSMaxim Sloyko .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT 138*038a9c3dSMaxim Sloyko | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, 139*038a9c3dSMaxim Sloyko .groups = attribute_groups, 140*038a9c3dSMaxim Sloyko }; 141*038a9c3dSMaxim Sloyko 142*038a9c3dSMaxim Sloyko static int isl68137_probe(struct i2c_client *client, 143*038a9c3dSMaxim Sloyko const struct i2c_device_id *id) 144*038a9c3dSMaxim Sloyko { 145*038a9c3dSMaxim Sloyko return pmbus_do_probe(client, id, &isl68137_info); 146*038a9c3dSMaxim Sloyko } 147*038a9c3dSMaxim Sloyko 148*038a9c3dSMaxim Sloyko static const struct i2c_device_id isl68137_id[] = { 149*038a9c3dSMaxim Sloyko {"isl68137", 0}, 150*038a9c3dSMaxim Sloyko {} 151*038a9c3dSMaxim Sloyko }; 152*038a9c3dSMaxim Sloyko 153*038a9c3dSMaxim Sloyko MODULE_DEVICE_TABLE(i2c, isl68137_id); 154*038a9c3dSMaxim Sloyko 155*038a9c3dSMaxim Sloyko /* This is the driver that will be inserted */ 156*038a9c3dSMaxim Sloyko static struct i2c_driver isl68137_driver = { 157*038a9c3dSMaxim Sloyko .driver = { 158*038a9c3dSMaxim Sloyko .name = "isl68137", 159*038a9c3dSMaxim Sloyko }, 160*038a9c3dSMaxim Sloyko .probe = isl68137_probe, 161*038a9c3dSMaxim Sloyko .remove = pmbus_do_remove, 162*038a9c3dSMaxim Sloyko .id_table = isl68137_id, 163*038a9c3dSMaxim Sloyko }; 164*038a9c3dSMaxim Sloyko 165*038a9c3dSMaxim Sloyko module_i2c_driver(isl68137_driver); 166*038a9c3dSMaxim Sloyko 167*038a9c3dSMaxim Sloyko MODULE_AUTHOR("Maxim Sloyko <maxims@google.com>"); 168*038a9c3dSMaxim Sloyko MODULE_DESCRIPTION("PMBus driver for Intersil ISL68137"); 169*038a9c3dSMaxim Sloyko MODULE_LICENSE("GPL"); 170