250 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Hardware monitoring driver for the STPDDC60 controller4 *5 * Copyright (c) 2021 Flextronics International Sweden AB.6 */7 8#include <linux/kernel.h>9#include <linux/module.h>10#include <linux/init.h>11#include <linux/err.h>12#include <linux/i2c.h>13#include <linux/pmbus.h>14#include "pmbus.h"15 16#define STPDDC60_MFR_READ_VOUT 0xd217#define STPDDC60_MFR_OV_LIMIT_OFFSET 0xe518#define STPDDC60_MFR_UV_LIMIT_OFFSET 0xe619 20static const struct i2c_device_id stpddc60_id[] = {21 {"stpddc60"},22 {"bmr481"},23 {}24};25MODULE_DEVICE_TABLE(i2c, stpddc60_id);26 27static struct pmbus_driver_info stpddc60_info = {28 .pages = 1,29 .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT30 | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT31 | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP32 | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT33 | PMBUS_HAVE_POUT,34};35 36/*37 * Calculate the closest absolute offset between commanded vout value38 * and limit value in steps of 50mv in the range 0 (50mv) to 7 (400mv).39 * Return 0 if the upper limit is lower than vout or if the lower limit40 * is higher than vout.41 */42static u8 stpddc60_get_offset(int vout, u16 limit, bool over)43{44 int offset;45 long v, l;46 47 v = 250 + (vout - 1) * 5; /* Convert VID to mv */48 l = (limit * 1000L) >> 8; /* Convert LINEAR to mv */49 50 if (over == (l < v))51 return 0;52 53 offset = DIV_ROUND_CLOSEST(abs(l - v), 50);54 55 if (offset > 0)56 offset--;57 58 return clamp_val(offset, 0, 7);59}60 61/*62 * Adjust the linear format word to use the given fixed exponent.63 */64static u16 stpddc60_adjust_linear(u16 word, s16 fixed)65{66 s16 e, m, d;67 68 e = ((s16)word) >> 11;69 m = ((s16)((word & 0x7ff) << 5)) >> 5;70 d = e - fixed;71 72 if (d >= 0)73 m <<= d;74 else75 m >>= -d;76 77 return clamp_val(m, 0, 0x3ff) | ((fixed << 11) & 0xf800);78}79 80/*81 * The VOUT_COMMAND register uses the VID format but the vout alarm limit82 * registers use the LINEAR format so we override VOUT_MODE here to force83 * LINEAR format for all registers.84 */85static int stpddc60_read_byte_data(struct i2c_client *client, int page, int reg)86{87 int ret;88 89 if (page > 0)90 return -ENXIO;91 92 switch (reg) {93 case PMBUS_VOUT_MODE:94 ret = 0x18;95 break;96 default:97 ret = -ENODATA;98 break;99 }100 101 return ret;102}103 104/*105 * The vout related registers return values in LINEAR11 format when LINEAR16106 * is expected. Clear the top 5 bits to set the exponent part to zero to107 * convert the value to LINEAR16 format.108 */109static int stpddc60_read_word_data(struct i2c_client *client, int page,110 int phase, int reg)111{112 int ret;113 114 if (page > 0)115 return -ENXIO;116 117 switch (reg) {118 case PMBUS_READ_VOUT:119 ret = pmbus_read_word_data(client, page, phase,120 STPDDC60_MFR_READ_VOUT);121 if (ret < 0)122 return ret;123 ret &= 0x7ff;124 break;125 case PMBUS_VOUT_OV_FAULT_LIMIT:126 case PMBUS_VOUT_UV_FAULT_LIMIT:127 ret = pmbus_read_word_data(client, page, phase, reg);128 if (ret < 0)129 return ret;130 ret &= 0x7ff;131 break;132 default:133 ret = -ENODATA;134 break;135 }136 137 return ret;138}139 140/*141 * The vout under- and over-voltage limits are set as an offset relative to142 * the commanded vout voltage. The vin, iout, pout and temp limits must use143 * the same fixed exponent the chip uses to encode the data when read.144 */145static int stpddc60_write_word_data(struct i2c_client *client, int page,146 int reg, u16 word)147{148 int ret;149 u8 offset;150 151 if (page > 0)152 return -ENXIO;153 154 switch (reg) {155 case PMBUS_VOUT_OV_FAULT_LIMIT:156 ret = pmbus_read_word_data(client, page, 0xff,157 PMBUS_VOUT_COMMAND);158 if (ret < 0)159 return ret;160 offset = stpddc60_get_offset(ret, word, true);161 ret = pmbus_write_byte_data(client, page,162 STPDDC60_MFR_OV_LIMIT_OFFSET,163 offset);164 break;165 case PMBUS_VOUT_UV_FAULT_LIMIT:166 ret = pmbus_read_word_data(client, page, 0xff,167 PMBUS_VOUT_COMMAND);168 if (ret < 0)169 return ret;170 offset = stpddc60_get_offset(ret, word, false);171 ret = pmbus_write_byte_data(client, page,172 STPDDC60_MFR_UV_LIMIT_OFFSET,173 offset);174 break;175 case PMBUS_VIN_OV_FAULT_LIMIT:176 case PMBUS_VIN_UV_FAULT_LIMIT:177 case PMBUS_OT_FAULT_LIMIT:178 case PMBUS_OT_WARN_LIMIT:179 case PMBUS_IOUT_OC_FAULT_LIMIT:180 case PMBUS_IOUT_OC_WARN_LIMIT:181 case PMBUS_POUT_OP_FAULT_LIMIT:182 ret = pmbus_read_word_data(client, page, 0xff, reg);183 if (ret < 0)184 return ret;185 word = stpddc60_adjust_linear(word, ret >> 11);186 ret = pmbus_write_word_data(client, page, reg, word);187 break;188 default:189 ret = -ENODATA;190 break;191 }192 193 return ret;194}195 196static int stpddc60_probe(struct i2c_client *client)197{198 int status;199 u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];200 const struct i2c_device_id *mid;201 struct pmbus_driver_info *info = &stpddc60_info;202 203 if (!i2c_check_functionality(client->adapter,204 I2C_FUNC_SMBUS_READ_BYTE_DATA205 | I2C_FUNC_SMBUS_BLOCK_DATA))206 return -ENODEV;207 208 status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);209 if (status < 0) {210 dev_err(&client->dev, "Failed to read Manufacturer Model\n");211 return status;212 }213 for (mid = stpddc60_id; mid->name[0]; mid++) {214 if (!strncasecmp(mid->name, device_id, strlen(mid->name)))215 break;216 }217 if (!mid->name[0]) {218 dev_err(&client->dev, "Unsupported device\n");219 return -ENODEV;220 }221 222 info->read_byte_data = stpddc60_read_byte_data;223 info->read_word_data = stpddc60_read_word_data;224 info->write_word_data = stpddc60_write_word_data;225 226 status = pmbus_do_probe(client, info);227 if (status < 0)228 return status;229 230 pmbus_set_update(client, PMBUS_VOUT_OV_FAULT_LIMIT, true);231 pmbus_set_update(client, PMBUS_VOUT_UV_FAULT_LIMIT, true);232 233 return 0;234}235 236static struct i2c_driver stpddc60_driver = {237 .driver = {238 .name = "stpddc60",239 },240 .probe = stpddc60_probe,241 .id_table = stpddc60_id,242};243 244module_i2c_driver(stpddc60_driver);245 246MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");247MODULE_DESCRIPTION("PMBus driver for ST STPDDC60");248MODULE_LICENSE("GPL");249MODULE_IMPORT_NS(PMBUS);250