133 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * sl28cpld hardware monitoring driver4 *5 * Copyright 2020 Kontron Europe GmbH6 */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 0x0018#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 the49 * scale. If the MSB is set, then the lower 7 bit has to be50 * 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 specification57 * says we should assume 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 struct hwmon_channel_info * const sl28cpld_hwmon_info[] = {71 HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),72 NULL73};74 75static const struct hwmon_ops sl28cpld_hwmon_ops = {76 .is_visible = sl28cpld_hwmon_is_visible,77 .read = sl28cpld_hwmon_read,78};79 80static const struct hwmon_chip_info sl28cpld_hwmon_chip_info = {81 .ops = &sl28cpld_hwmon_ops,82 .info = sl28cpld_hwmon_info,83};84 85static int sl28cpld_hwmon_probe(struct platform_device *pdev)86{87 struct sl28cpld_hwmon *hwmon;88 struct device *hwmon_dev;89 int ret;90 91 if (!pdev->dev.parent)92 return -ENODEV;93 94 hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);95 if (!hwmon)96 return -ENOMEM;97 98 hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL);99 if (!hwmon->regmap)100 return -ENODEV;101 102 ret = device_property_read_u32(&pdev->dev, "reg", &hwmon->offset);103 if (ret)104 return -EINVAL;105 106 hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,107 "sl28cpld_hwmon", hwmon,108 &sl28cpld_hwmon_chip_info, NULL);109 if (IS_ERR(hwmon_dev))110 dev_err(&pdev->dev, "failed to register as hwmon device");111 112 return PTR_ERR_OR_ZERO(hwmon_dev);113}114 115static const struct of_device_id sl28cpld_hwmon_of_match[] = {116 { .compatible = "kontron,sl28cpld-fan" },117 {}118};119MODULE_DEVICE_TABLE(of, sl28cpld_hwmon_of_match);120 121static struct platform_driver sl28cpld_hwmon_driver = {122 .probe = sl28cpld_hwmon_probe,123 .driver = {124 .name = "sl28cpld-fan",125 .of_match_table = sl28cpld_hwmon_of_match,126 },127};128module_platform_driver(sl28cpld_hwmon_driver);129 130MODULE_DESCRIPTION("sl28cpld Hardware Monitoring Driver");131MODULE_AUTHOR("Michael Walle <michael@walle.cc>");132MODULE_LICENSE("GPL");133