mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 675 mass ave cambridge ma 02139 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 441 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Michael Ellerman <mpe@ellerman.id.au> (powerpc) Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190520071858.739733335@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
246 lines
7.1 KiB
C
246 lines
7.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Hardware monitoring driver for PMBus devices
|
|
*
|
|
* Copyright (c) 2010, 2011 Ericsson AB.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/pmbus.h>
|
|
#include "pmbus.h"
|
|
|
|
struct pmbus_device_info {
|
|
int pages;
|
|
u32 flags;
|
|
};
|
|
|
|
/*
|
|
* Find sensor groups and status registers on each page.
|
|
*/
|
|
static void pmbus_find_sensor_groups(struct i2c_client *client,
|
|
struct pmbus_driver_info *info)
|
|
{
|
|
int page;
|
|
|
|
/* Sensors detected on page 0 only */
|
|
if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN))
|
|
info->func[0] |= PMBUS_HAVE_VIN;
|
|
if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP))
|
|
info->func[0] |= PMBUS_HAVE_VCAP;
|
|
if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN))
|
|
info->func[0] |= PMBUS_HAVE_IIN;
|
|
if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN))
|
|
info->func[0] |= PMBUS_HAVE_PIN;
|
|
if (info->func[0]
|
|
&& pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT))
|
|
info->func[0] |= PMBUS_HAVE_STATUS_INPUT;
|
|
if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_12) &&
|
|
pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) {
|
|
info->func[0] |= PMBUS_HAVE_FAN12;
|
|
if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12))
|
|
info->func[0] |= PMBUS_HAVE_STATUS_FAN12;
|
|
}
|
|
if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_34) &&
|
|
pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) {
|
|
info->func[0] |= PMBUS_HAVE_FAN34;
|
|
if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34))
|
|
info->func[0] |= PMBUS_HAVE_STATUS_FAN34;
|
|
}
|
|
if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1))
|
|
info->func[0] |= PMBUS_HAVE_TEMP;
|
|
if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_2))
|
|
info->func[0] |= PMBUS_HAVE_TEMP2;
|
|
if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_3))
|
|
info->func[0] |= PMBUS_HAVE_TEMP3;
|
|
if (info->func[0] & (PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2
|
|
| PMBUS_HAVE_TEMP3)
|
|
&& pmbus_check_byte_register(client, 0,
|
|
PMBUS_STATUS_TEMPERATURE))
|
|
info->func[0] |= PMBUS_HAVE_STATUS_TEMP;
|
|
|
|
/* Sensors detected on all pages */
|
|
for (page = 0; page < info->pages; page++) {
|
|
if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) {
|
|
info->func[page] |= PMBUS_HAVE_VOUT;
|
|
if (pmbus_check_byte_register(client, page,
|
|
PMBUS_STATUS_VOUT))
|
|
info->func[page] |= PMBUS_HAVE_STATUS_VOUT;
|
|
}
|
|
if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) {
|
|
info->func[page] |= PMBUS_HAVE_IOUT;
|
|
if (pmbus_check_byte_register(client, 0,
|
|
PMBUS_STATUS_IOUT))
|
|
info->func[page] |= PMBUS_HAVE_STATUS_IOUT;
|
|
}
|
|
if (pmbus_check_word_register(client, page, PMBUS_READ_POUT))
|
|
info->func[page] |= PMBUS_HAVE_POUT;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Identify chip parameters.
|
|
*/
|
|
static int pmbus_identify(struct i2c_client *client,
|
|
struct pmbus_driver_info *info)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!info->pages) {
|
|
/*
|
|
* Check if the PAGE command is supported. If it is,
|
|
* keep setting the page number until it fails or until the
|
|
* maximum number of pages has been reached. Assume that
|
|
* this is the number of pages supported by the chip.
|
|
*/
|
|
if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) {
|
|
int page;
|
|
|
|
for (page = 1; page < PMBUS_PAGES; page++) {
|
|
if (pmbus_set_page(client, page) < 0)
|
|
break;
|
|
}
|
|
pmbus_set_page(client, 0);
|
|
info->pages = page;
|
|
} else {
|
|
info->pages = 1;
|
|
}
|
|
|
|
pmbus_clear_faults(client);
|
|
}
|
|
|
|
if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) {
|
|
int vout_mode;
|
|
|
|
vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
|
|
if (vout_mode >= 0 && vout_mode != 0xff) {
|
|
switch (vout_mode >> 5) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
info->format[PSC_VOLTAGE_OUT] = vid;
|
|
info->vrm_version = vr11;
|
|
break;
|
|
case 2:
|
|
info->format[PSC_VOLTAGE_OUT] = direct;
|
|
break;
|
|
default:
|
|
ret = -ENODEV;
|
|
goto abort;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We should check if the COEFFICIENTS register is supported.
|
|
* If it is, and the chip is configured for direct mode, we can read
|
|
* the coefficients from the chip, one set per group of sensor
|
|
* registers.
|
|
*
|
|
* To do this, we will need access to a chip which actually supports the
|
|
* COEFFICIENTS command, since the command is too complex to implement
|
|
* without testing it. Until then, abort if a chip configured for direct
|
|
* mode was detected.
|
|
*/
|
|
if (info->format[PSC_VOLTAGE_OUT] == direct) {
|
|
ret = -ENODEV;
|
|
goto abort;
|
|
}
|
|
|
|
/* Try to find sensor groups */
|
|
pmbus_find_sensor_groups(client, info);
|
|
abort:
|
|
return ret;
|
|
}
|
|
|
|
static int pmbus_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct pmbus_driver_info *info;
|
|
struct pmbus_platform_data *pdata = NULL;
|
|
struct device *dev = &client->dev;
|
|
struct pmbus_device_info *device_info;
|
|
|
|
info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
device_info = (struct pmbus_device_info *)id->driver_data;
|
|
if (device_info->flags & PMBUS_SKIP_STATUS_CHECK) {
|
|
pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
|
|
GFP_KERNEL);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
|
|
pdata->flags = PMBUS_SKIP_STATUS_CHECK;
|
|
}
|
|
|
|
info->pages = device_info->pages;
|
|
info->identify = pmbus_identify;
|
|
dev->platform_data = pdata;
|
|
|
|
return pmbus_do_probe(client, id, info);
|
|
}
|
|
|
|
static const struct pmbus_device_info pmbus_info_one = {
|
|
.pages = 1,
|
|
.flags = 0
|
|
};
|
|
static const struct pmbus_device_info pmbus_info_zero = {
|
|
.pages = 0,
|
|
.flags = 0
|
|
};
|
|
static const struct pmbus_device_info pmbus_info_one_skip = {
|
|
.pages = 1,
|
|
.flags = PMBUS_SKIP_STATUS_CHECK
|
|
};
|
|
|
|
/*
|
|
* Use driver_data to set the number of pages supported by the chip.
|
|
*/
|
|
static const struct i2c_device_id pmbus_id[] = {
|
|
{"adp4000", (kernel_ulong_t)&pmbus_info_one},
|
|
{"bmr453", (kernel_ulong_t)&pmbus_info_one},
|
|
{"bmr454", (kernel_ulong_t)&pmbus_info_one},
|
|
{"dps460", (kernel_ulong_t)&pmbus_info_one_skip},
|
|
{"dps650ab", (kernel_ulong_t)&pmbus_info_one_skip},
|
|
{"dps800", (kernel_ulong_t)&pmbus_info_one_skip},
|
|
{"mdt040", (kernel_ulong_t)&pmbus_info_one},
|
|
{"ncp4200", (kernel_ulong_t)&pmbus_info_one},
|
|
{"ncp4208", (kernel_ulong_t)&pmbus_info_one},
|
|
{"pdt003", (kernel_ulong_t)&pmbus_info_one},
|
|
{"pdt006", (kernel_ulong_t)&pmbus_info_one},
|
|
{"pdt012", (kernel_ulong_t)&pmbus_info_one},
|
|
{"pmbus", (kernel_ulong_t)&pmbus_info_zero},
|
|
{"sgd009", (kernel_ulong_t)&pmbus_info_one_skip},
|
|
{"tps40400", (kernel_ulong_t)&pmbus_info_one},
|
|
{"tps544b20", (kernel_ulong_t)&pmbus_info_one},
|
|
{"tps544b25", (kernel_ulong_t)&pmbus_info_one},
|
|
{"tps544c20", (kernel_ulong_t)&pmbus_info_one},
|
|
{"tps544c25", (kernel_ulong_t)&pmbus_info_one},
|
|
{"udt020", (kernel_ulong_t)&pmbus_info_one},
|
|
{}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, pmbus_id);
|
|
|
|
/* This is the driver that will be inserted */
|
|
static struct i2c_driver pmbus_driver = {
|
|
.driver = {
|
|
.name = "pmbus",
|
|
},
|
|
.probe = pmbus_probe,
|
|
.remove = pmbus_do_remove,
|
|
.id_table = pmbus_id,
|
|
};
|
|
|
|
module_i2c_driver(pmbus_driver);
|
|
|
|
MODULE_AUTHOR("Guenter Roeck");
|
|
MODULE_DESCRIPTION("Generic PMBus driver");
|
|
MODULE_LICENSE("GPL");
|