linux/drivers/hwmon/sl28cpld-hwmon.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * sl28cpld hardware monitoring driver
   4 *
   5 * Copyright 2020 Kontron Europe GmbH
   6 */
   7
   8#include <linux/bitfield.h>
   9#include <linux/hwmon.h>
  10#include <linux/kernel.h>
  11#include <linux/mod_devicetable.h>
  12#include <linux/module.h>
  13#include <linux/platform_device.h>
  14#include <linux/property.h>
  15#include <linux/regmap.h>
  16
  17#define FAN_INPUT               0x00
  18#define   FAN_SCALE_X8          BIT(7)
  19#define   FAN_VALUE_MASK        GENMASK(6, 0)
  20
  21struct sl28cpld_hwmon {
  22        struct regmap *regmap;
  23        u32 offset;
  24};
  25
  26static umode_t sl28cpld_hwmon_is_visible(const void *data,
  27                                         enum hwmon_sensor_types type,
  28                                         u32 attr, int channel)
  29{
  30        return 0444;
  31}
  32
  33static int sl28cpld_hwmon_read(struct device *dev,
  34                               enum hwmon_sensor_types type, u32 attr,
  35                               int channel, long *input)
  36{
  37        struct sl28cpld_hwmon *hwmon = dev_get_drvdata(dev);
  38        unsigned int value;
  39        int ret;
  40
  41        switch (attr) {
  42        case hwmon_fan_input:
  43                ret = regmap_read(hwmon->regmap, hwmon->offset + FAN_INPUT,
  44                                  &value);
  45                if (ret)
  46                        return ret;
  47                /*
  48                 * The register has a 7 bit value and 1 bit which indicates the
  49                 * scale. If the MSB is set, then the lower 7 bit has to be
  50                 * multiplied by 8, to get the correct reading.
  51                 */
  52                if (value & FAN_SCALE_X8)
  53                        value = FIELD_GET(FAN_VALUE_MASK, value) << 3;
  54
  55                /*
  56                 * The counter period is 1000ms and the sysfs specification
  57                 * says we should asssume 2 pulses per revolution.
  58                 */
  59                value *= 60 / 2;
  60
  61                break;
  62        default:
  63                return -EOPNOTSUPP;
  64        }
  65
  66        *input = value;
  67        return 0;
  68}
  69
  70static const u32 sl28cpld_hwmon_fan_config[] = {
  71        HWMON_F_INPUT,
  72        0
  73};
  74
  75static const struct hwmon_channel_info sl28cpld_hwmon_fan = {
  76        .type = hwmon_fan,
  77        .config = sl28cpld_hwmon_fan_config,
  78};
  79
  80static const struct hwmon_channel_info *sl28cpld_hwmon_info[] = {
  81        &sl28cpld_hwmon_fan,
  82        NULL
  83};
  84
  85static const struct hwmon_ops sl28cpld_hwmon_ops = {
  86        .is_visible = sl28cpld_hwmon_is_visible,
  87        .read = sl28cpld_hwmon_read,
  88};
  89
  90static const struct hwmon_chip_info sl28cpld_hwmon_chip_info = {
  91        .ops = &sl28cpld_hwmon_ops,
  92        .info = sl28cpld_hwmon_info,
  93};
  94
  95static int sl28cpld_hwmon_probe(struct platform_device *pdev)
  96{
  97        struct sl28cpld_hwmon *hwmon;
  98        struct device *hwmon_dev;
  99        int ret;
 100
 101        if (!pdev->dev.parent)
 102                return -ENODEV;
 103
 104        hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);
 105        if (!hwmon)
 106                return -ENOMEM;
 107
 108        hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
 109        if (!hwmon->regmap)
 110                return -ENODEV;
 111
 112        ret = device_property_read_u32(&pdev->dev, "reg", &hwmon->offset);
 113        if (ret)
 114                return -EINVAL;
 115
 116        hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
 117                                "sl28cpld_hwmon", hwmon,
 118                                &sl28cpld_hwmon_chip_info, NULL);
 119        if (IS_ERR(hwmon_dev))
 120                dev_err(&pdev->dev, "failed to register as hwmon device");
 121
 122        return PTR_ERR_OR_ZERO(hwmon_dev);
 123}
 124
 125static const struct of_device_id sl28cpld_hwmon_of_match[] = {
 126        { .compatible = "kontron,sl28cpld-fan" },
 127        {}
 128};
 129MODULE_DEVICE_TABLE(of, sl28cpld_hwmon_of_match);
 130
 131static struct platform_driver sl28cpld_hwmon_driver = {
 132        .probe = sl28cpld_hwmon_probe,
 133        .driver = {
 134                .name = "sl28cpld-fan",
 135                .of_match_table = sl28cpld_hwmon_of_match,
 136        },
 137};
 138module_platform_driver(sl28cpld_hwmon_driver);
 139
 140MODULE_DESCRIPTION("sl28cpld Hardware Monitoring Driver");
 141MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
 142MODULE_LICENSE("GPL");
 143