root/drivers/hwmon/pmbus/pxe1610.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Hardware monitoring driver for Infineon PXE1610
 *
 * Copyright (c) 2019 Facebook Inc
 *
 */

#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "pmbus.h"

#define PXE1610_NUM_PAGES 3

/* Identify chip parameters. */
static int pxe1610_identify(struct i2c_client *client,
                             struct pmbus_driver_info *info)
{
        int i;

        for (i = 0; i < PXE1610_NUM_PAGES; i++) {
                if (pmbus_check_byte_register(client, i, PMBUS_VOUT_MODE)) {
                        u8 vout_mode;
                        int ret;

                        /* Read the register with VOUT scaling value.*/
                        ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE);
                        if (ret < 0)
                                return ret;

                        vout_mode = ret & GENMASK(4, 0);

                        switch (vout_mode) {
                        case 1:
                                info->vrm_version[i] = vr12;
                                break;
                        case 2:
                                info->vrm_version[i] = vr13;
                                break;
                        default:
                                /*
                                 * If prior pages are available limit operation
                                 * to them
                                 */
                                if (i != 0) {
                                        info->pages = i;
                                        return 0;
                                }

                                return -ENODEV;
                        }
                }
        }

        return 0;
}

static struct pmbus_driver_info pxe1610_info = {
        .pages = PXE1610_NUM_PAGES,
        .format[PSC_VOLTAGE_IN] = linear,
        .format[PSC_VOLTAGE_OUT] = vid,
        .format[PSC_CURRENT_IN] = linear,
        .format[PSC_CURRENT_OUT] = linear,
        .format[PSC_TEMPERATURE] = linear,
        .format[PSC_POWER] = linear,
        .func[0] = PMBUS_HAVE_VIN
                | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
                | PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
                | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
                | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
                | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
        .func[1] = PMBUS_HAVE_VIN
                | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
                | PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
                | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
                | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
                | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
        .func[2] = PMBUS_HAVE_VIN
                | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
                | PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
                | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
                | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
                | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
        .identify = pxe1610_identify,
};

static int pxe1610_probe(struct i2c_client *client)
{
        struct pmbus_driver_info *info;
        u8 buf[I2C_SMBUS_BLOCK_MAX];
        int ret;

        if (!i2c_check_functionality(
                        client->adapter,
                        I2C_FUNC_SMBUS_READ_BYTE_DATA
                        | I2C_FUNC_SMBUS_READ_WORD_DATA
                        | I2C_FUNC_SMBUS_READ_BLOCK_DATA))
                return -ENODEV;

        /*
         * By default this device doesn't boot to page 0, so set page 0
         * to access all pmbus registers.
         */
        ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
        if (ret < 0)
                return dev_err_probe(&client->dev, ret,
                                     "Failed to set page 0\n");

        /* Read Manufacturer id */
        ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
        if (ret < 0) {
                dev_err(&client->dev, "Failed to read PMBUS_MFR_ID\n");
                return ret;
        }
        if (ret != 2 || strncmp(buf, "XP", 2)) {
                dev_err(&client->dev, "MFR_ID unrecognized\n");
                return -ENODEV;
        }

        info = devm_kmemdup(&client->dev, &pxe1610_info,
                            sizeof(struct pmbus_driver_info),
                            GFP_KERNEL);
        if (!info)
                return -ENOMEM;

        return pmbus_do_probe(client, info);
}

static const struct i2c_device_id pxe1610_id[] = {
        {"pxe1610"},
        {"pxe1110"},
        {"pxm1310"},
        {}
};

MODULE_DEVICE_TABLE(i2c, pxe1610_id);

static struct i2c_driver pxe1610_driver = {
        .driver = {
                        .name = "pxe1610",
                        },
        .probe = pxe1610_probe,
        .id_table = pxe1610_id,
};

module_i2c_driver(pxe1610_driver);

MODULE_AUTHOR("Vijay Khemka <vijaykhemka@fb.com>");
MODULE_DESCRIPTION("PMBus driver for Infineon PXE1610, PXE1110 and PXM1310");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");