Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Hardware monitoring driver for the STPDDC60 controller
0004  *
0005  * Copyright (c) 2021 Flextronics International Sweden AB.
0006  */
0007 
0008 #include <linux/kernel.h>
0009 #include <linux/module.h>
0010 #include <linux/init.h>
0011 #include <linux/err.h>
0012 #include <linux/i2c.h>
0013 #include <linux/pmbus.h>
0014 #include "pmbus.h"
0015 
0016 #define STPDDC60_MFR_READ_VOUT      0xd2
0017 #define STPDDC60_MFR_OV_LIMIT_OFFSET    0xe5
0018 #define STPDDC60_MFR_UV_LIMIT_OFFSET    0xe6
0019 
0020 static const struct i2c_device_id stpddc60_id[] = {
0021     {"stpddc60", 0},
0022     {"bmr481", 0},
0023     {}
0024 };
0025 MODULE_DEVICE_TABLE(i2c, stpddc60_id);
0026 
0027 static struct pmbus_driver_info stpddc60_info = {
0028     .pages = 1,
0029     .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
0030         | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
0031         | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP
0032         | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
0033         | PMBUS_HAVE_POUT,
0034 };
0035 
0036 /*
0037  * Calculate the closest absolute offset between commanded vout value
0038  * and limit value in steps of 50mv in the range 0 (50mv) to 7 (400mv).
0039  * Return 0 if the upper limit is lower than vout or if the lower limit
0040  * is higher than vout.
0041  */
0042 static u8 stpddc60_get_offset(int vout, u16 limit, bool over)
0043 {
0044     int offset;
0045     long v, l;
0046 
0047     v = 250 + (vout - 1) * 5; /* Convert VID to mv */
0048     l = (limit * 1000L) >> 8; /* Convert LINEAR to mv */
0049 
0050     if (over == (l < v))
0051         return 0;
0052 
0053     offset = DIV_ROUND_CLOSEST(abs(l - v), 50);
0054 
0055     if (offset > 0)
0056         offset--;
0057 
0058     return clamp_val(offset, 0, 7);
0059 }
0060 
0061 /*
0062  * Adjust the linear format word to use the given fixed exponent.
0063  */
0064 static u16 stpddc60_adjust_linear(u16 word, s16 fixed)
0065 {
0066     s16 e, m, d;
0067 
0068     e = ((s16)word) >> 11;
0069     m = ((s16)((word & 0x7ff) << 5)) >> 5;
0070     d = e - fixed;
0071 
0072     if (d >= 0)
0073         m <<= d;
0074     else
0075         m >>= -d;
0076 
0077     return clamp_val(m, 0, 0x3ff) | ((fixed << 11) & 0xf800);
0078 }
0079 
0080 /*
0081  * The VOUT_COMMAND register uses the VID format but the vout alarm limit
0082  * registers use the LINEAR format so we override VOUT_MODE here to force
0083  * LINEAR format for all registers.
0084  */
0085 static int stpddc60_read_byte_data(struct i2c_client *client, int page, int reg)
0086 {
0087     int ret;
0088 
0089     if (page > 0)
0090         return -ENXIO;
0091 
0092     switch (reg) {
0093     case PMBUS_VOUT_MODE:
0094         ret = 0x18;
0095         break;
0096     default:
0097         ret = -ENODATA;
0098         break;
0099     }
0100 
0101     return ret;
0102 }
0103 
0104 /*
0105  * The vout related registers return values in LINEAR11 format when LINEAR16
0106  * is expected. Clear the top 5 bits to set the exponent part to zero to
0107  * convert the value to LINEAR16 format.
0108  */
0109 static int stpddc60_read_word_data(struct i2c_client *client, int page,
0110                    int phase, int reg)
0111 {
0112     int ret;
0113 
0114     if (page > 0)
0115         return -ENXIO;
0116 
0117     switch (reg) {
0118     case PMBUS_READ_VOUT:
0119         ret = pmbus_read_word_data(client, page, phase,
0120                        STPDDC60_MFR_READ_VOUT);
0121         if (ret < 0)
0122             return ret;
0123         ret &= 0x7ff;
0124         break;
0125     case PMBUS_VOUT_OV_FAULT_LIMIT:
0126     case PMBUS_VOUT_UV_FAULT_LIMIT:
0127         ret = pmbus_read_word_data(client, page, phase, reg);
0128         if (ret < 0)
0129             return ret;
0130         ret &= 0x7ff;
0131         break;
0132     default:
0133         ret = -ENODATA;
0134         break;
0135     }
0136 
0137     return ret;
0138 }
0139 
0140 /*
0141  * The vout under- and over-voltage limits are set as an offset relative to
0142  * the commanded vout voltage. The vin, iout, pout and temp limits must use
0143  * the same fixed exponent the chip uses to encode the data when read.
0144  */
0145 static int stpddc60_write_word_data(struct i2c_client *client, int page,
0146                     int reg, u16 word)
0147 {
0148     int ret;
0149     u8 offset;
0150 
0151     if (page > 0)
0152         return -ENXIO;
0153 
0154     switch (reg) {
0155     case PMBUS_VOUT_OV_FAULT_LIMIT:
0156         ret = pmbus_read_word_data(client, page, 0xff,
0157                        PMBUS_VOUT_COMMAND);
0158         if (ret < 0)
0159             return ret;
0160         offset = stpddc60_get_offset(ret, word, true);
0161         ret = pmbus_write_byte_data(client, page,
0162                         STPDDC60_MFR_OV_LIMIT_OFFSET,
0163                         offset);
0164         break;
0165     case PMBUS_VOUT_UV_FAULT_LIMIT:
0166         ret = pmbus_read_word_data(client, page, 0xff,
0167                        PMBUS_VOUT_COMMAND);
0168         if (ret < 0)
0169             return ret;
0170         offset = stpddc60_get_offset(ret, word, false);
0171         ret = pmbus_write_byte_data(client, page,
0172                         STPDDC60_MFR_UV_LIMIT_OFFSET,
0173                         offset);
0174         break;
0175     case PMBUS_VIN_OV_FAULT_LIMIT:
0176     case PMBUS_VIN_UV_FAULT_LIMIT:
0177     case PMBUS_OT_FAULT_LIMIT:
0178     case PMBUS_OT_WARN_LIMIT:
0179     case PMBUS_IOUT_OC_FAULT_LIMIT:
0180     case PMBUS_IOUT_OC_WARN_LIMIT:
0181     case PMBUS_POUT_OP_FAULT_LIMIT:
0182         ret = pmbus_read_word_data(client, page, 0xff, reg);
0183         if (ret < 0)
0184             return ret;
0185         word = stpddc60_adjust_linear(word, ret >> 11);
0186         ret = pmbus_write_word_data(client, page, reg, word);
0187         break;
0188     default:
0189         ret = -ENODATA;
0190         break;
0191     }
0192 
0193     return ret;
0194 }
0195 
0196 static int stpddc60_probe(struct i2c_client *client)
0197 {
0198     int status;
0199     u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
0200     const struct i2c_device_id *mid;
0201     struct pmbus_driver_info *info = &stpddc60_info;
0202 
0203     if (!i2c_check_functionality(client->adapter,
0204                      I2C_FUNC_SMBUS_READ_BYTE_DATA
0205                      | I2C_FUNC_SMBUS_BLOCK_DATA))
0206         return -ENODEV;
0207 
0208     status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
0209     if (status < 0) {
0210         dev_err(&client->dev, "Failed to read Manufacturer Model\n");
0211         return status;
0212     }
0213     for (mid = stpddc60_id; mid->name[0]; mid++) {
0214         if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
0215             break;
0216     }
0217     if (!mid->name[0]) {
0218         dev_err(&client->dev, "Unsupported device\n");
0219         return -ENODEV;
0220     }
0221 
0222     info->read_byte_data = stpddc60_read_byte_data;
0223     info->read_word_data = stpddc60_read_word_data;
0224     info->write_word_data = stpddc60_write_word_data;
0225 
0226     status = pmbus_do_probe(client, info);
0227     if (status < 0)
0228         return status;
0229 
0230     pmbus_set_update(client, PMBUS_VOUT_OV_FAULT_LIMIT, true);
0231     pmbus_set_update(client, PMBUS_VOUT_UV_FAULT_LIMIT, true);
0232 
0233     return 0;
0234 }
0235 
0236 static struct i2c_driver stpddc60_driver = {
0237     .driver = {
0238            .name = "stpddc60",
0239            },
0240     .probe_new = stpddc60_probe,
0241     .id_table = stpddc60_id,
0242 };
0243 
0244 module_i2c_driver(stpddc60_driver);
0245 
0246 MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
0247 MODULE_DESCRIPTION("PMBus driver for ST STPDDC60");
0248 MODULE_LICENSE("GPL");
0249 MODULE_IMPORT_NS(PMBUS);