Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * sl67mcu hardware monitoring driver
4 *
5 * Copyright 2025 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 SA67MCU_VOLTAGE(n) (0x00 + ((n) * 2))
18#define SA67MCU_TEMP(n) (0x04 + ((n) * 2))
19
20struct sa67mcu_hwmon {
21 struct regmap *regmap;
22 u32 offset;
23};
24
25static int sa67mcu_hwmon_read(struct device *dev,
26 enum hwmon_sensor_types type, u32 attr,
27 int channel, long *input)
28{
29 struct sa67mcu_hwmon *hwmon = dev_get_drvdata(dev);
30 unsigned int offset;
31 u8 reg[2];
32 int ret;
33
34 switch (type) {
35 case hwmon_in:
36 switch (attr) {
37 case hwmon_in_input:
38 offset = hwmon->offset + SA67MCU_VOLTAGE(channel);
39 break;
40 default:
41 return -EOPNOTSUPP;
42 }
43 break;
44 case hwmon_temp:
45 switch (attr) {
46 case hwmon_temp_input:
47 offset = hwmon->offset + SA67MCU_TEMP(channel);
48 break;
49 default:
50 return -EOPNOTSUPP;
51 }
52 break;
53 default:
54 return -EOPNOTSUPP;
55 }
56
57 /* Reading the low byte will capture the value */
58 ret = regmap_bulk_read(hwmon->regmap, offset, reg, ARRAY_SIZE(reg));
59 if (ret)
60 return ret;
61
62 *input = reg[1] << 8 | reg[0];
63
64 /* Temperatures are s16 and in 0.1degC steps. */
65 if (type == hwmon_temp)
66 *input = sign_extend32(*input, 15) * 100;
67
68 return 0;
69}
70
71static const struct hwmon_channel_info * const sa67mcu_hwmon_info[] = {
72 HWMON_CHANNEL_INFO(in,
73 HWMON_I_INPUT | HWMON_I_LABEL,
74 HWMON_I_INPUT | HWMON_I_LABEL),
75 HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
76 NULL
77};
78
79static const char *const sa67mcu_hwmon_in_labels[] = {
80 "VDDIN",
81 "VDD_RTC",
82};
83
84static int sa67mcu_hwmon_read_string(struct device *dev,
85 enum hwmon_sensor_types type, u32 attr,
86 int channel, const char **str)
87{
88 switch (type) {
89 case hwmon_in:
90 switch (attr) {
91 case hwmon_in_label:
92 *str = sa67mcu_hwmon_in_labels[channel];
93 return 0;
94 default:
95 return -EOPNOTSUPP;
96 }
97 default:
98 return -EOPNOTSUPP;
99 }
100}
101
102static const struct hwmon_ops sa67mcu_hwmon_ops = {
103 .visible = 0444,
104 .read = sa67mcu_hwmon_read,
105 .read_string = sa67mcu_hwmon_read_string,
106};
107
108static const struct hwmon_chip_info sa67mcu_hwmon_chip_info = {
109 .ops = &sa67mcu_hwmon_ops,
110 .info = sa67mcu_hwmon_info,
111};
112
113static int sa67mcu_hwmon_probe(struct platform_device *pdev)
114{
115 struct sa67mcu_hwmon *hwmon;
116 struct device *hwmon_dev;
117 int ret;
118
119 if (!pdev->dev.parent)
120 return -ENODEV;
121
122 hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);
123 if (!hwmon)
124 return -ENOMEM;
125
126 hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
127 if (!hwmon->regmap)
128 return -ENODEV;
129
130 ret = device_property_read_u32(&pdev->dev, "reg", &hwmon->offset);
131 if (ret)
132 return -EINVAL;
133
134 hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
135 "sa67mcu_hwmon", hwmon,
136 &sa67mcu_hwmon_chip_info,
137 NULL);
138 if (IS_ERR(hwmon_dev))
139 dev_err(&pdev->dev, "failed to register as hwmon device");
140
141 return PTR_ERR_OR_ZERO(hwmon_dev);
142}
143
144static const struct of_device_id sa67mcu_hwmon_of_match[] = {
145 { .compatible = "kontron,sa67mcu-hwmon", },
146 {}
147};
148MODULE_DEVICE_TABLE(of, sa67mcu_hwmon_of_match);
149
150static struct platform_driver sa67mcu_hwmon_driver = {
151 .probe = sa67mcu_hwmon_probe,
152 .driver = {
153 .name = "sa67mcu-hwmon",
154 .of_match_table = sa67mcu_hwmon_of_match,
155 },
156};
157module_platform_driver(sa67mcu_hwmon_driver);
158
159MODULE_DESCRIPTION("sa67mcu Hardware Monitoring Driver");
160MODULE_AUTHOR("Michael Walle <mwalle@kernel.org>");
161MODULE_LICENSE("GPL");