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