xref: /openbmc/linux/drivers/hwmon/oxp-sensors.c (revision ecfb9f40)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Platform driver for OXP Handhelds that expose fan reading and control
4  * via hwmon sysfs.
5  *
6  * Old boards have the same DMI strings and they are told appart by the
7  * boot cpu vendor (Intel/AMD). Currently only AMD boards are supported
8  * but the code is made to be simple to add other handheld boards in the
9  * future.
10  * Fan control is provided via pwm interface in the range [0-255].
11  * Old AMD boards use [0-100] as range in the EC, the written value is
12  * scaled to accommodate for that. Newer boards like the mini PRO and
13  * AOK ZOE are not scaled but have the same EC layout.
14  *
15  * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>
16  */
17 
18 #include <linux/acpi.h>
19 #include <linux/dev_printk.h>
20 #include <linux/dmi.h>
21 #include <linux/hwmon.h>
22 #include <linux/init.h>
23 #include <linux/kernel.h>
24 #include <linux/module.h>
25 #include <linux/platform_device.h>
26 #include <linux/processor.h>
27 
28 /* Handle ACPI lock mechanism */
29 static u32 oxp_mutex;
30 
31 #define ACPI_LOCK_DELAY_MS	500
32 
33 static bool lock_global_acpi_lock(void)
34 {
35 	return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex));
36 }
37 
38 static bool unlock_global_acpi_lock(void)
39 {
40 	return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex));
41 }
42 
43 enum oxp_board {
44 	aok_zoe_a1 = 1,
45 	oxp_mini_amd,
46 	oxp_mini_amd_pro,
47 };
48 
49 static enum oxp_board board;
50 
51 #define OXP_SENSOR_FAN_REG		0x76 /* Fan reading is 2 registers long */
52 #define OXP_SENSOR_PWM_ENABLE_REG	0x4A /* PWM enable is 1 register long */
53 #define OXP_SENSOR_PWM_REG		0x4B /* PWM reading is 1 register long */
54 
55 static const struct dmi_system_id dmi_table[] = {
56 	{
57 		.matches = {
58 			DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
59 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"),
60 		},
61 		.driver_data = (void *) &(enum oxp_board) {aok_zoe_a1},
62 	},
63 	{
64 		.matches = {
65 			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
66 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),
67 		},
68 		.driver_data = (void *) &(enum oxp_board) {oxp_mini_amd},
69 	},
70 	{
71 		.matches = {
72 			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
73 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),
74 		},
75 		.driver_data = (void *) &(enum oxp_board) {oxp_mini_amd_pro},
76 	},
77 	{},
78 };
79 
80 /* Helper functions to handle EC read/write */
81 static int read_from_ec(u8 reg, int size, long *val)
82 {
83 	int i;
84 	int ret;
85 	u8 buffer;
86 
87 	if (!lock_global_acpi_lock())
88 		return -EBUSY;
89 
90 	*val = 0;
91 	for (i = 0; i < size; i++) {
92 		ret = ec_read(reg + i, &buffer);
93 		if (ret)
94 			return ret;
95 		*val <<= i * 8;
96 		*val += buffer;
97 	}
98 
99 	if (!unlock_global_acpi_lock())
100 		return -EBUSY;
101 
102 	return 0;
103 }
104 
105 static int write_to_ec(const struct device *dev, u8 reg, u8 value)
106 {
107 	int ret;
108 
109 	if (!lock_global_acpi_lock())
110 		return -EBUSY;
111 
112 	ret = ec_write(reg, value);
113 
114 	if (!unlock_global_acpi_lock())
115 		return -EBUSY;
116 
117 	return ret;
118 }
119 
120 static int oxp_pwm_enable(const struct device *dev)
121 {
122 	return write_to_ec(dev, OXP_SENSOR_PWM_ENABLE_REG, 0x01);
123 }
124 
125 static int oxp_pwm_disable(const struct device *dev)
126 {
127 	return write_to_ec(dev, OXP_SENSOR_PWM_ENABLE_REG, 0x00);
128 }
129 
130 /* Callbacks for hwmon interface */
131 static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
132 				       enum hwmon_sensor_types type, u32 attr, int channel)
133 {
134 	switch (type) {
135 	case hwmon_fan:
136 		return 0444;
137 	case hwmon_pwm:
138 		return 0644;
139 	default:
140 		return 0;
141 	}
142 }
143 
144 static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
145 			     u32 attr, int channel, long *val)
146 {
147 	int ret;
148 
149 	switch (type) {
150 	case hwmon_fan:
151 		switch (attr) {
152 		case hwmon_fan_input:
153 			return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
154 		default:
155 			break;
156 		}
157 		break;
158 	case hwmon_pwm:
159 		switch (attr) {
160 		case hwmon_pwm_input:
161 			ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
162 			if (ret)
163 				return ret;
164 			if (board == oxp_mini_amd)
165 				*val = (*val * 255) / 100;
166 			return 0;
167 		case hwmon_pwm_enable:
168 			return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
169 		default:
170 			break;
171 		}
172 		break;
173 	default:
174 		break;
175 	}
176 	return -EOPNOTSUPP;
177 }
178 
179 static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
180 			      u32 attr, int channel, long val)
181 {
182 	switch (type) {
183 	case hwmon_pwm:
184 		switch (attr) {
185 		case hwmon_pwm_enable:
186 			if (val == 1)
187 				return oxp_pwm_enable(dev);
188 			else if (val == 0)
189 				return oxp_pwm_disable(dev);
190 			return -EINVAL;
191 		case hwmon_pwm_input:
192 			if (val < 0 || val > 255)
193 				return -EINVAL;
194 			if (board == oxp_mini_amd)
195 				val = (val * 100) / 255;
196 			return write_to_ec(dev, OXP_SENSOR_PWM_REG, val);
197 		default:
198 			break;
199 		}
200 		break;
201 	default:
202 		break;
203 	}
204 	return -EOPNOTSUPP;
205 }
206 
207 /* Known sensors in the OXP EC controllers */
208 static const struct hwmon_channel_info *oxp_platform_sensors[] = {
209 	HWMON_CHANNEL_INFO(fan,
210 			   HWMON_F_INPUT),
211 	HWMON_CHANNEL_INFO(pwm,
212 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
213 	NULL,
214 };
215 
216 static const struct hwmon_ops oxp_ec_hwmon_ops = {
217 	.is_visible = oxp_ec_hwmon_is_visible,
218 	.read = oxp_platform_read,
219 	.write = oxp_platform_write,
220 };
221 
222 static const struct hwmon_chip_info oxp_ec_chip_info = {
223 	.ops = &oxp_ec_hwmon_ops,
224 	.info = oxp_platform_sensors,
225 };
226 
227 /* Initialization logic */
228 static int oxp_platform_probe(struct platform_device *pdev)
229 {
230 	const struct dmi_system_id *dmi_entry;
231 	struct device *dev = &pdev->dev;
232 	struct device *hwdev;
233 
234 	/*
235 	 * Have to check for AMD processor here because DMI strings are the
236 	 * same between Intel and AMD boards, the only way to tell them appart
237 	 * is the CPU.
238 	 * Intel boards seem to have different EC registers and values to
239 	 * read/write.
240 	 */
241 	dmi_entry = dmi_first_match(dmi_table);
242 	if (!dmi_entry || boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
243 		return -ENODEV;
244 
245 	board = *((enum oxp_board *) dmi_entry->driver_data);
246 
247 	hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,
248 						     &oxp_ec_chip_info, NULL);
249 
250 	return PTR_ERR_OR_ZERO(hwdev);
251 }
252 
253 static struct platform_driver oxp_platform_driver = {
254 	.driver = {
255 		.name = "oxp-platform",
256 	},
257 	.probe = oxp_platform_probe,
258 };
259 
260 static struct platform_device *oxp_platform_device;
261 
262 static int __init oxp_platform_init(void)
263 {
264 	oxp_platform_device =
265 		platform_create_bundle(&oxp_platform_driver,
266 				       oxp_platform_probe, NULL, 0, NULL, 0);
267 
268 	return PTR_ERR_OR_ZERO(oxp_platform_device);
269 }
270 
271 static void __exit oxp_platform_exit(void)
272 {
273 	platform_device_unregister(oxp_platform_device);
274 	platform_driver_unregister(&oxp_platform_driver);
275 }
276 
277 MODULE_DEVICE_TABLE(dmi, dmi_table);
278 
279 module_init(oxp_platform_init);
280 module_exit(oxp_platform_exit);
281 
282 MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
283 MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
284 MODULE_LICENSE("GPL");
285