Skip to content

Commit

Permalink
pmbus: Add new tps544 power regulator driver
Browse files Browse the repository at this point in the history
Add new tps544 power regulator driver. This driver supports voltage
read & write and current read & calibration functionality for
TPS544B25 part.

Signed-off-by: Harini Katakam <harini.katakam@xilinx.com>
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
State: pending
  • Loading branch information
harini-katakam authored and Michal Simek committed May 27, 2021
1 parent 5fd2c95 commit 0bae0f6
Show file tree
Hide file tree
Showing 3 changed files with 385 additions and 0 deletions.
20 changes: 20 additions & 0 deletions drivers/hwmon/pmbus/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,26 @@ config SENSORS_TPS53679
This driver can also be built as a module. If so, the module will
be called tps53679.

config SENSORS_TPS544
tristate "TI TPS544"
help
If you say yes here you get hardware monitoring support for Texas
Instruments TPS544.

This driver can also be built as a module. If so, the module will
be called tps544.

config SENSORS_TPS544_REGULATOR
bool "Regulator support for TPS544"
depends on SENSORS_TPS544
select REGULATOR
help
If you say yes here you get regulator support for Texas Instruments
TPS544.

This driver is dependent on TPS544 sensor and it can also be built
as a module.

config SENSORS_UCD9000
tristate "TI UCD90120, UCD90124, UCD90160, UCD90320, UCD9090, UCD90910"
help
Expand Down
1 change: 1 addition & 0 deletions drivers/hwmon/pmbus/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o
obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o
obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
obj-$(CONFIG_SENSORS_TPS544) += tps544.o
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o
Expand Down
364 changes: 364 additions & 0 deletions drivers/hwmon/pmbus/tps544.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,364 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TPS544B25 power regulator driver
*
* Copyright (C) 2019 Xilinx, Inc.
*
*/

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

#define TPS544_NUM_PAGES 1

struct tps544_data {
struct device *dev;
u16 vout_min[TPS544_NUM_PAGES], vout_max[TPS544_NUM_PAGES];
struct pmbus_driver_info info;
};

struct vlut {
int vol;
u16 vloop;
u16 v_ovfault;
u16 v_ovwarn;
u16 vmax;
u16 mfr_vmin;
u16 v_uvwarn;
u16 v_uvfault;
};

#if IS_ENABLED(CONFIG_SENSORS_TPS544_REGULATOR)
#define TPS544_MFR_VOUT_MIN 0xA4
#define TPS544_MFR_RESTORE_DEF_ALL 0x12
#define TPS544_MFR_IOUT_CAL_OFFSET 0x39

#define TPS544_VOUTREAD_MULTIPLIER 1950
#define TPS544_IOUTREAD_MULTIPLIER 62500
#define TPS544_IOUTREAD_MASK GENMASK(9, 0)

#define TPS544_VOUT_LIMIT 5300000

#define to_tps544_data(x) container_of(x, struct tps544_data, info)

/*
* This currently supports 3 voltage out buckets:
* 0.5V to 1.3V
* 1.3V to 2.6V
* 2.6V to 5.3V
* Any requested voltage will be mapped to one of these buckets and
* VOUT will be programmed with 0.1V granularity.
*/
static const struct vlut tps544_vout[3] = {
{500000, 0xF004, 0x0290, 0x0285, 0x0300, 0x0100, 0x00CD, 0x009A},
{1300000, 0xF002, 0x059A, 0x0566, 0x0600, 0x0100, 0x0143, 0x0130},
{2600000, 0xF001, 0x0B00, 0x0A9A, 0x0A00, 0x0100, 0x0143, 0x0130}
};
#endif

static int tps544_read_word_data(struct i2c_client *client, int page, int reg)
{
return pmbus_read_word_data(client, page, reg);
}

static int tps544_read_byte_data(struct i2c_client *client, int page, int reg)
{
return pmbus_read_byte_data(client, page, reg);
}

static int tps544_write_byte(struct i2c_client *client, int page, u8 byte)
{
return pmbus_write_byte(client, page, byte);
}

static int tps544_write_word_data(struct i2c_client *client, int page,
int reg, u16 word)
{
int ret;

ret = pmbus_write_word_data(client, page, reg, word);
/* TODO - Define new PMBUS virtual register entries for these */

return ret;
}

#if IS_ENABLED(CONFIG_SENSORS_TPS544_REGULATOR)
static int tps544_regulator_get_voltage(struct regulator_dev *rdev)
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
int page = 0;

return pmbus_read_word_data(client, page, PMBUS_READ_VOUT);
}

static int tps544_regulator_set_voltage(struct regulator_dev *rdev, int min_uV,
int max_uV, unsigned int *selector)
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct tps544_data *data = to_tps544_data(info);
int index, page = 0;
u16 vout;

/* voltage will be set close to min value requested */
vout = (u16)((min_uV * 512) / 1000000);

/* Check voltage bucket */
if (min_uV >= tps544_vout[2].vol)
index = 2;
else if (min_uV >= tps544_vout[1].vol)
index = 1;
else if (min_uV >= tps544_vout[0].vol)
index = 0;
else
return -EINVAL;

pmbus_write_word_data(client, page, PMBUS_VOUT_SCALE_LOOP,
tps544_vout[index].vloop);
/* Use delay after setting scale loop; this is derived from testing */
msleep(2000);
pmbus_write_word_data(client, page, PMBUS_VOUT_OV_FAULT_LIMIT,
tps544_vout[index].v_ovfault);
pmbus_write_word_data(client, page, PMBUS_VOUT_OV_WARN_LIMIT,
tps544_vout[index].v_ovwarn);
pmbus_write_word_data(client, page, PMBUS_VOUT_MAX,
tps544_vout[index].vmax);
pmbus_write_word_data(client, page, PMBUS_VOUT_COMMAND, vout);
tps544_write_word_data(client, page, TPS544_MFR_VOUT_MIN,
tps544_vout[index].mfr_vmin);
pmbus_write_word_data(client, page, PMBUS_VOUT_UV_WARN_LIMIT,
tps544_vout[index].v_uvwarn);
pmbus_write_word_data(client, page, PMBUS_VOUT_UV_FAULT_LIMIT,
tps544_vout[index].v_uvfault);

data->vout_min[page] = min_uV;
data->vout_max[page] = max_uV;

return 0;
}

static ssize_t tps544_setv_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct regulator_dev *rdev = dev_get_drvdata(dev);
int vout;

vout = tps544_regulator_get_voltage(rdev) * TPS544_VOUTREAD_MULTIPLIER;
return sprintf(buf, "%d\n", vout);
}

static ssize_t tps544_setv_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct regulator_dev *rdev = dev_get_drvdata(dev);
int val;
int err;

err = kstrtoint(buf, 0, &val);
if (err)
return err;
if (val > TPS544_VOUT_LIMIT)
return -EINVAL;

err = tps544_regulator_set_voltage(rdev, val, val, NULL);
if (err)
return err;

return count;
}

static DEVICE_ATTR_RW(tps544_setv);

static ssize_t tps544_restorev_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev->parent);
int err;

err = pmbus_write_byte(client, 0, TPS544_MFR_RESTORE_DEF_ALL);
if (err)
return err;

return count;
}

static DEVICE_ATTR_WO(tps544_restorev);

static ssize_t tps544_geti_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev->parent);
u16 reg_iout;

reg_iout = pmbus_read_word_data(client, 0, PMBUS_READ_IOUT) &
TPS544_IOUTREAD_MASK;

return sprintf(buf, "%d\n", reg_iout * TPS544_IOUTREAD_MULTIPLIER);
}

static DEVICE_ATTR_RO(tps544_geti);

static ssize_t tps544_setcali_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev->parent);
int reg_cali;

reg_cali = pmbus_read_word_data(client, 0, TPS544_MFR_IOUT_CAL_OFFSET);

return sprintf(buf, "Current: 0x%x; Set value in hex to calibrate\n",
reg_cali);
}

static ssize_t tps544_setcali_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev->parent);
u16 val;
int err;

err = kstrtou16(buf, 0x0, &val);
if (err)
return err;

err = pmbus_write_word_data(client, 0, TPS544_MFR_IOUT_CAL_OFFSET, val);
if (err)
return err;

return (ssize_t)count;
}

static DEVICE_ATTR_RW(tps544_setcali);

static struct attribute *reg_attrs[] = {
&dev_attr_tps544_setv.attr,
&dev_attr_tps544_restorev.attr,
&dev_attr_tps544_geti.attr,
&dev_attr_tps544_setcali.attr,
NULL,
};

ATTRIBUTE_GROUPS(reg);

static const struct regulator_desc tps544_reg_desc[] = {
PMBUS_REGULATOR("vout", 0),
};
#endif /* CONFIG_SENSORS_TPS544_REGULATOR */

static int tps544_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
unsigned int i;
struct device *dev = &client->dev;
struct tps544_data *data;
struct pmbus_driver_info *info;
#if IS_ENABLED(CONFIG_SENSORS_TPS544_REGULATOR)
int ret;
struct regulator_dev *rdev;
struct regulator_config rconfig = { };
#endif

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

data = devm_kzalloc(dev, sizeof(struct tps544_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;

data->dev = dev;

info = &data->info;
info->write_word_data = tps544_write_word_data;
/* TODO - remove these 3 hooks maybe unnecessary */
info->write_byte = tps544_write_byte;
info->read_word_data = tps544_read_word_data;
info->read_byte_data = tps544_read_byte_data;

for (i = 0; i < ARRAY_SIZE(data->vout_min); i++)
data->vout_min[i] = 0xffff;

info->pages = TPS544_NUM_PAGES;
info->func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;

#if IS_ENABLED(CONFIG_SENSORS_TPS544_REGULATOR)
rconfig.dev = dev;
rconfig.driver_data = data;
info->num_regulators = info->pages;
info->reg_desc = tps544_reg_desc;
if (info->num_regulators > (int)ARRAY_SIZE(tps544_reg_desc)) {
dev_err(&client->dev, "num_regulators too large!");
info->num_regulators = ARRAY_SIZE(tps544_reg_desc);
}

rdev = devm_regulator_register(dev, tps544_reg_desc, &rconfig);
if (IS_ERR(rdev)) {
dev_err(dev, "Failed to register %s regulator\n",
info->reg_desc[0].name);
return (int)PTR_ERR(rdev);
}

ret = sysfs_create_groups(&rdev->dev.kobj, reg_groups);
if (ret)
return ret;

dev_set_drvdata(dev, rdev);
#endif

return pmbus_do_probe(client, id, info);
}

static int tps544_remove(struct i2c_client *client)
{
#if IS_ENABLED(CONFIG_SENSORS_TPS544_REGULATOR)
struct device *dev = &client->dev;
struct regulator_dev *rdev = dev_get_drvdata(dev);

sysfs_remove_groups(&rdev->dev.kobj, reg_groups);
#endif
pmbus_do_remove(client);

return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id tps544_of_match[] = {
{ .compatible = "ti,tps544" },
{ }
};
MODULE_DEVICE_TABLE(of, tps544_of_match);
#endif

static const struct i2c_device_id tps544_id[] = {
{"tps544", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, tps544_id);

static struct i2c_driver tps544_driver = {
.driver = {
.name = "tps544",
.of_match_table = of_match_ptr(tps544_of_match),
},
.probe = tps544_probe,
.remove = tps544_remove,
.id_table = tps544_id,
};

module_i2c_driver(tps544_driver);

MODULE_AUTHOR("Harini Katakam");
MODULE_DESCRIPTION("PMBus regulator driver for TPS544");
MODULE_LICENSE("GPL v2");

0 comments on commit 0bae0f6

Please sign in to comment.