aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/sl28cpld-hwmon.c
blob: 9ce4899a81a55c4df18030ae16f57b41b6a3a449 (plain) (blame)
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
// 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 assume 2 pulses per revolution.
		 */
		value *= 60 / 2;

		break;
	default:
		return -EOPNOTSUPP;
	}

	*input = value;
	return 0;
}

static const struct hwmon_channel_info *sl28cpld_hwmon_info[] = {
	HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
	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");