aboutsummaryrefslogtreecommitdiff
path: root/cds9k-fan.c
blob: 8a58aa066f7cc330f09a3b2941c3d9ad4f51601e (plain)
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
143
144
145
146
147
148
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <linux/hwmon.h>

#define FAN_DUTY 0x0
#define FAN_TACH 0x1

struct cds9k_fan {
	struct regmap *regmap;
	u32 base;
};

static unsigned int cds9k_rpm_to_pwm(long rpm)
{
	static long max_rpm = 5000;
	static unsigned int max_pwm = 255;

	rpm = clamp(rpm, (long)0, max_rpm);

	return max_pwm * rpm / max_rpm;
}

static umode_t cds9k_fan_is_visible(const void *fan, enum hwmon_sensor_types type, u32 attr, int channel)
{
	if (channel != 0)
		return 0;

	if (type == hwmon_fan && attr == hwmon_fan_input)
		return 0444;

	if (type == hwmon_fan && attr == hwmon_fan_target)
		return 0644;

	return 0;
}

static int cds9k_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *value)
{
	int ret;
	struct cds9k_fan *fan = dev_get_drvdata(dev);
	u32 reg_offs;
	unsigned int raw_value;
	if (channel != 0 || type != hwmon_fan)
		return -EINVAL;

	switch (attr) {
		case hwmon_fan_target:
			reg_offs = FAN_DUTY;
			break;
		case hwmon_fan_input:
			reg_offs = FAN_TACH;
			break;
		default:
			return -EINVAL;
	}

	ret = regmap_read(fan->regmap, fan->base + reg_offs, &raw_value);
	if (ret)
		return ret;

	*value = raw_value;
	return 0;
}

static int cds9k_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long value)
{
	unsigned int target_pwm;
	struct cds9k_fan *fan = dev_get_drvdata(dev);

	if (channel != 0 || type != hwmon_fan || attr != hwmon_fan_target)
		return -EINVAL;

	target_pwm = cds9k_rpm_to_pwm(value);
	return regmap_write(fan->regmap, fan->base + FAN_DUTY, target_pwm);
}

static const struct hwmon_ops cds9k_fan_ops = {
	.is_visible = cds9k_fan_is_visible,
	.read = cds9k_fan_read,
	.write = cds9k_fan_write
};

static const struct hwmon_channel_info *cds9k_fan_channel_info[] = {
	HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
	HWMON_CHANNEL_INFO(fan, HWMON_F_TARGET),
	NULL
};

static const struct hwmon_chip_info cds9k_fan_chip_info = {
	.ops = &cds9k_fan_ops,
	.info = cds9k_fan_channel_info
};

static int cds9k_fan_probe(struct platform_device *pdev)
{
	struct device *hwmon_dev;
	int ret;
	struct cds9k_fan *fan;
	const char *label;

	fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL);
	if (!fan)
		return -ENOMEM;

	ret = device_property_read_u32(&pdev->dev, "reg", &fan->base);
	ret |= device_property_read_string(&pdev->dev, "label", &label);
	if (ret)
		return -EINVAL;

	if (!pdev->dev.parent)
		return -ENODEV;

	fan->regmap = dev_get_regmap(pdev->dev.parent, NULL);
	if (!fan->regmap)
		return -ENODEV;

	dev_info(&pdev->dev, "registering hwmon device\n");
	hwmon_dev = devm_hwmon_device_register_with_info(
		&pdev->dev,
		label,
		fan,
		&cds9k_fan_chip_info,
		NULL
	);

	return PTR_ERR_OR_ZERO(hwmon_dev);
}

static struct of_device_id cds9k_fan_of_match[] = {
	{ .compatible = "david,cds9k-fan" },
	{}
};
MODULE_DEVICE_TABLE(of, cds9k_fan_of_match);

static struct platform_driver cds9k_fan = {
	.driver = {
		.name = "cds9k-fan",
		.of_match_table = cds9k_fan_of_match,
	},
	.probe = cds9k_fan_probe,
};
module_platform_driver(cds9k_fan);

MODULE_AUTHOR("David Phillips <david@yeah.nah.nz>");
MODULE_DESCRIPTION("LED driver for the CDS9K board controller");
MODULE_LICENSE("GPL v2");