#include <linux/auxiliary_bus.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/hwmon.h>
#include <linux/io.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_data/usb-xhci-prom21.h>
#include <linux/pm_runtime.h>
#define PROM21_XHCI_INDEX_OFFSET 0x3000
#define PROM21_XHCI_DATA_OFFSET 0x3008
#define PROM21_XHCI_TEMP_SELECTOR 0x0001e520
struct prom21_xhci {
struct pci_dev *pdev;
struct device *hwmon_dev;
void __iomem *regs;
};
static int prom21_xhci_pm_get(struct prom21_xhci *hwmon)
{
struct device *dev = &hwmon->pdev->dev;
int ret;
ret = pm_runtime_get_if_active(dev);
if (ret > 0)
return 0;
if (ret == -EINVAL) {
if (pm_runtime_status_suspended(dev))
return -ENODATA;
pm_runtime_get_noresume(dev);
return 0;
}
if (!ret)
return -ENODATA;
return ret;
}
static int prom21_xhci_read_temp_raw_restore_index(struct prom21_xhci *hwmon,
u8 *raw)
{
struct device *dev = &hwmon->pdev->dev;
u32 index;
u8 data;
int ret;
ret = prom21_xhci_pm_get(hwmon);
if (ret)
return ret;
index = readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
writel(PROM21_XHCI_TEMP_SELECTOR,
hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
data = readl(hwmon->regs + PROM21_XHCI_DATA_OFFSET) & 0xff;
writel(index, hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET);
pm_runtime_put(dev);
if (!data)
return -ENODATA;
*raw = data;
return 0;
}
static long prom21_xhci_raw_to_millicelsius(u8 raw)
{
return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624;
}
static umode_t prom21_xhci_is_visible(const void *drvdata,
enum hwmon_sensor_types type, u32 attr,
int channel)
{
if (type != hwmon_temp)
return 0;
switch (attr) {
case hwmon_temp_input:
return 0444;
default:
return 0;
}
}
static int prom21_xhci_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct prom21_xhci *hwmon = dev_get_drvdata(dev);
u8 raw;
int ret;
if (type != hwmon_temp || attr != hwmon_temp_input)
return -EOPNOTSUPP;
ret = prom21_xhci_read_temp_raw_restore_index(hwmon, &raw);
if (ret)
return ret;
*val = prom21_xhci_raw_to_millicelsius(raw);
return 0;
}
static const struct hwmon_ops prom21_xhci_ops = {
.is_visible = prom21_xhci_is_visible,
.read = prom21_xhci_read,
};
static const struct hwmon_channel_info *const prom21_xhci_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
NULL,
};
static const struct hwmon_chip_info prom21_xhci_chip_info = {
.ops = &prom21_xhci_ops,
.info = prom21_xhci_info,
};
static int prom21_xhci_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
struct device *dev = &auxdev->dev;
const struct prom21_xhci_pdata *pdata = dev_get_platdata(dev);
struct prom21_xhci *hwmon;
if (!pdata)
return dev_err_probe(dev, -ENODEV,
"platform data unavailable\n");
if (!pdata->regs ||
pdata->rsrc_len < PROM21_XHCI_DATA_OFFSET + sizeof(u32))
return dev_err_probe(dev, -ENODEV, "invalid MMIO resource\n");
hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
if (!hwmon)
return -ENOMEM;
hwmon->pdev = pdata->pdev;
hwmon->regs = pdata->regs;
auxiliary_set_drvdata(auxdev, hwmon);
hwmon->hwmon_dev =
hwmon_device_register_with_info(&pdata->pdev->dev, "prom21_xhci",
hwmon, &prom21_xhci_chip_info,
NULL);
if (IS_ERR(hwmon->hwmon_dev))
return PTR_ERR(hwmon->hwmon_dev);
return 0;
}
static void prom21_xhci_remove(struct auxiliary_device *auxdev)
{
struct prom21_xhci *hwmon = auxiliary_get_drvdata(auxdev);
hwmon_device_unregister(hwmon->hwmon_dev);
}
static const struct auxiliary_device_id prom21_xhci_id_table[] = {
{ .name = "xhci_pci_prom21.hwmon" },
{}
};
MODULE_DEVICE_TABLE(auxiliary, prom21_xhci_id_table);
static struct auxiliary_driver prom21_xhci_driver = {
.name = "prom21-xhci",
.probe = prom21_xhci_probe,
.remove = prom21_xhci_remove,
.id_table = prom21_xhci_id_table,
};
module_auxiliary_driver(prom21_xhci_driver);
MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>");
MODULE_DESCRIPTION("AMD Promontory 21 xHCI temperature sensor driver");
MODULE_LICENSE("GPL");