]>
Commit | Line | Data |
---|---|---|
b23688ae SH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * System Control and Management Interface(SCMI) based hwmon sensor driver | |
4 | * | |
5 | * Copyright (C) 2018 ARM Ltd. | |
6 | * Sudeep Holla <sudeep.holla@arm.com> | |
7 | */ | |
8 | ||
9 | #include <linux/hwmon.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/scmi_protocol.h> | |
12 | #include <linux/slab.h> | |
13 | #include <linux/sysfs.h> | |
14 | #include <linux/thermal.h> | |
15 | ||
16 | struct scmi_sensors { | |
17 | const struct scmi_handle *handle; | |
18 | const struct scmi_sensor_info **info[hwmon_max]; | |
19 | }; | |
20 | ||
21 | static int scmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, | |
22 | u32 attr, int channel, long *val) | |
23 | { | |
24 | int ret; | |
25 | u64 value; | |
26 | const struct scmi_sensor_info *sensor; | |
27 | struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev); | |
28 | const struct scmi_handle *h = scmi_sensors->handle; | |
29 | ||
30 | sensor = *(scmi_sensors->info[type] + channel); | |
31 | ret = h->sensor_ops->reading_get(h, sensor->id, false, &value); | |
32 | if (!ret) | |
33 | *val = value; | |
34 | ||
35 | return ret; | |
36 | } | |
37 | ||
38 | static int | |
39 | scmi_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, | |
40 | u32 attr, int channel, const char **str) | |
41 | { | |
42 | const struct scmi_sensor_info *sensor; | |
43 | struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev); | |
44 | ||
45 | sensor = *(scmi_sensors->info[type] + channel); | |
46 | *str = sensor->name; | |
47 | ||
48 | return 0; | |
49 | } | |
50 | ||
51 | static umode_t | |
52 | scmi_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, | |
53 | u32 attr, int channel) | |
54 | { | |
55 | const struct scmi_sensor_info *sensor; | |
56 | const struct scmi_sensors *scmi_sensors = drvdata; | |
57 | ||
58 | sensor = *(scmi_sensors->info[type] + channel); | |
59 | if (sensor && sensor->name) | |
60 | return S_IRUGO; | |
61 | ||
62 | return 0; | |
63 | } | |
64 | ||
65 | static const struct hwmon_ops scmi_hwmon_ops = { | |
66 | .is_visible = scmi_hwmon_is_visible, | |
67 | .read = scmi_hwmon_read, | |
68 | .read_string = scmi_hwmon_read_string, | |
69 | }; | |
70 | ||
71 | static struct hwmon_chip_info scmi_chip_info = { | |
72 | .ops = &scmi_hwmon_ops, | |
73 | .info = NULL, | |
74 | }; | |
75 | ||
76 | static int scmi_hwmon_add_chan_info(struct hwmon_channel_info *scmi_hwmon_chan, | |
77 | struct device *dev, int num, | |
78 | enum hwmon_sensor_types type, u32 config) | |
79 | { | |
80 | int i; | |
81 | u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); | |
82 | ||
83 | if (!cfg) | |
84 | return -ENOMEM; | |
85 | ||
86 | scmi_hwmon_chan->type = type; | |
87 | scmi_hwmon_chan->config = cfg; | |
88 | for (i = 0; i < num; i++, cfg++) | |
89 | *cfg = config; | |
90 | ||
91 | return 0; | |
92 | } | |
93 | ||
94 | static enum hwmon_sensor_types scmi_types[] = { | |
95 | [TEMPERATURE_C] = hwmon_temp, | |
96 | [VOLTAGE] = hwmon_in, | |
97 | [CURRENT] = hwmon_curr, | |
98 | [POWER] = hwmon_power, | |
99 | [ENERGY] = hwmon_energy, | |
100 | }; | |
101 | ||
102 | static u32 hwmon_attributes[] = { | |
103 | [hwmon_chip] = HWMON_C_REGISTER_TZ, | |
104 | [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, | |
105 | [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, | |
106 | [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, | |
107 | [hwmon_power] = HWMON_P_INPUT | HWMON_P_LABEL, | |
108 | [hwmon_energy] = HWMON_E_INPUT | HWMON_E_LABEL, | |
109 | }; | |
110 | ||
111 | static int scmi_hwmon_probe(struct scmi_device *sdev) | |
112 | { | |
113 | int i, idx; | |
114 | u16 nr_sensors; | |
115 | enum hwmon_sensor_types type; | |
116 | struct scmi_sensors *scmi_sensors; | |
117 | const struct scmi_sensor_info *sensor; | |
118 | int nr_count[hwmon_max] = {0}, nr_types = 0; | |
119 | const struct hwmon_chip_info *chip_info; | |
120 | struct device *hwdev, *dev = &sdev->dev; | |
121 | struct hwmon_channel_info *scmi_hwmon_chan; | |
122 | const struct hwmon_channel_info **ptr_scmi_ci; | |
123 | const struct scmi_handle *handle = sdev->handle; | |
124 | ||
125 | if (!handle || !handle->sensor_ops) | |
126 | return -ENODEV; | |
127 | ||
128 | nr_sensors = handle->sensor_ops->count_get(handle); | |
129 | if (!nr_sensors) | |
130 | return -EIO; | |
131 | ||
132 | scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL); | |
133 | if (!scmi_sensors) | |
134 | return -ENOMEM; | |
135 | ||
136 | scmi_sensors->handle = handle; | |
137 | ||
138 | for (i = 0; i < nr_sensors; i++) { | |
139 | sensor = handle->sensor_ops->info_get(handle, i); | |
140 | if (!sensor) | |
c09880ce | 141 | return -EINVAL; |
b23688ae SH |
142 | |
143 | switch (sensor->type) { | |
144 | case TEMPERATURE_C: | |
145 | case VOLTAGE: | |
146 | case CURRENT: | |
147 | case POWER: | |
148 | case ENERGY: | |
149 | type = scmi_types[sensor->type]; | |
150 | if (!nr_count[type]) | |
151 | nr_types++; | |
152 | nr_count[type]++; | |
153 | break; | |
154 | } | |
155 | } | |
156 | ||
157 | if (nr_count[hwmon_temp]) | |
158 | nr_count[hwmon_chip]++, nr_types++; | |
159 | ||
160 | scmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*scmi_hwmon_chan), | |
161 | GFP_KERNEL); | |
162 | if (!scmi_hwmon_chan) | |
163 | return -ENOMEM; | |
164 | ||
165 | ptr_scmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*ptr_scmi_ci), | |
166 | GFP_KERNEL); | |
167 | if (!ptr_scmi_ci) | |
168 | return -ENOMEM; | |
169 | ||
170 | scmi_chip_info.info = ptr_scmi_ci; | |
171 | chip_info = &scmi_chip_info; | |
172 | ||
f18a36cf SH |
173 | for (type = 0; type < hwmon_max; type++) { |
174 | if (!nr_count[type]) | |
175 | continue; | |
176 | ||
b23688ae SH |
177 | scmi_hwmon_add_chan_info(scmi_hwmon_chan, dev, nr_count[type], |
178 | type, hwmon_attributes[type]); | |
179 | *ptr_scmi_ci++ = scmi_hwmon_chan++; | |
180 | ||
181 | scmi_sensors->info[type] = | |
182 | devm_kcalloc(dev, nr_count[type], | |
183 | sizeof(*scmi_sensors->info), GFP_KERNEL); | |
184 | if (!scmi_sensors->info[type]) | |
185 | return -ENOMEM; | |
186 | } | |
187 | ||
188 | for (i = nr_sensors - 1; i >= 0 ; i--) { | |
189 | sensor = handle->sensor_ops->info_get(handle, i); | |
190 | if (!sensor) | |
191 | continue; | |
192 | ||
193 | switch (sensor->type) { | |
194 | case TEMPERATURE_C: | |
195 | case VOLTAGE: | |
196 | case CURRENT: | |
197 | case POWER: | |
198 | case ENERGY: | |
199 | type = scmi_types[sensor->type]; | |
200 | idx = --nr_count[type]; | |
201 | *(scmi_sensors->info[type] + idx) = sensor; | |
202 | break; | |
203 | } | |
204 | } | |
205 | ||
206 | hwdev = devm_hwmon_device_register_with_info(dev, "scmi_sensors", | |
207 | scmi_sensors, chip_info, | |
208 | NULL); | |
209 | ||
210 | return PTR_ERR_OR_ZERO(hwdev); | |
211 | } | |
212 | ||
213 | static const struct scmi_device_id scmi_id_table[] = { | |
214 | { SCMI_PROTOCOL_SENSOR }, | |
215 | { }, | |
216 | }; | |
217 | MODULE_DEVICE_TABLE(scmi, scmi_id_table); | |
218 | ||
219 | static struct scmi_driver scmi_hwmon_drv = { | |
220 | .name = "scmi-hwmon", | |
221 | .probe = scmi_hwmon_probe, | |
222 | .id_table = scmi_id_table, | |
223 | }; | |
224 | module_scmi_driver(scmi_hwmon_drv); | |
225 | ||
226 | MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); | |
227 | MODULE_DESCRIPTION("ARM SCMI HWMON interface driver"); | |
228 | MODULE_LICENSE("GPL v2"); |