Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.4.
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * Platform driver for OneXPlayer, AOK ZOE, and Aya Neo Handhelds that expose
  4 * fan reading and control via hwmon sysfs.
  5 *
  6 * Old OXP boards have the same DMI strings and they are told apart by
  7 * the boot cpu vendor (Intel/AMD). Currently only AMD boards are
  8 * supported but the code is made to be simple to add other handheld
  9 * boards in the 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/dmi.h>
 20#include <linux/hwmon.h>
 21#include <linux/init.h>
 22#include <linux/kernel.h>
 23#include <linux/module.h>
 24#include <linux/platform_device.h>
 25#include <linux/processor.h>
 26
 27/* Handle ACPI lock mechanism */
 28static u32 oxp_mutex;
 29
 30#define ACPI_LOCK_DELAY_MS	500
 31
 32static bool lock_global_acpi_lock(void)
 33{
 34	return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex));
 35}
 36
 37static bool unlock_global_acpi_lock(void)
 38{
 39	return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex));
 40}
 41
 42enum oxp_board {
 43	aok_zoe_a1 = 1,
 44	aya_neo_2,
 45	aya_neo_air,
 46	aya_neo_air_pro,
 47	aya_neo_geek,
 48	oxp_mini_amd,
 49	oxp_mini_amd_a07,
 50	oxp_mini_amd_pro,
 51};
 52
 53static enum oxp_board board;
 54
 55/* Fan reading and PWM */
 56#define OXP_SENSOR_FAN_REG		0x76 /* Fan reading is 2 registers long */
 57#define OXP_SENSOR_PWM_ENABLE_REG	0x4A /* PWM enable is 1 register long */
 58#define OXP_SENSOR_PWM_REG		0x4B /* PWM reading is 1 register long */
 59
 60/* Turbo button takeover function
 61 * Older boards have different values and EC registers
 62 * for the same function
 63 */
 64#define OXP_OLD_TURBO_SWITCH_REG	0x1E
 65#define OXP_OLD_TURBO_TAKE_VAL		0x01
 66#define OXP_OLD_TURBO_RETURN_VAL	0x00
 67
 68#define OXP_TURBO_SWITCH_REG		0xF1
 69#define OXP_TURBO_TAKE_VAL		0x40
 70#define OXP_TURBO_RETURN_VAL		0x00
 71
 72static const struct dmi_system_id dmi_table[] = {
 73	{
 74		.matches = {
 75			DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
 76			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"),
 77		},
 78		.driver_data = (void *)aok_zoe_a1,
 79	},
 80	{
 81		.matches = {
 82			DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
 83			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"),
 84		},
 85		.driver_data = (void *)aok_zoe_a1,
 86	},
 87	{
 88		.matches = {
 89			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
 90			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
 91		},
 92		.driver_data = (void *)aya_neo_2,
 93	},
 94	{
 95		.matches = {
 96			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
 97			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
 98		},
 99		.driver_data = (void *)aya_neo_air,
100	},
101	{
102		.matches = {
103			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
104			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
105		},
106		.driver_data = (void *)aya_neo_air_pro,
107	},
108	{
109		.matches = {
110			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
111			DMI_EXACT_MATCH(DMI_BOARD_NAME, "GEEK"),
112		},
113		.driver_data = (void *)aya_neo_geek,
114	},
115	{
116		.matches = {
117			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
118			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),
119		},
120		.driver_data = (void *)oxp_mini_amd,
121	},
122	{
123		.matches = {
124			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
125			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"),
126		},
127		.driver_data = (void *)oxp_mini_amd_a07,
128	},
129	{
130		.matches = {
131			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
132			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),
133		},
134		.driver_data = (void *)oxp_mini_amd_pro,
135	},
136	{},
137};
138
139/* Helper functions to handle EC read/write */
140static int read_from_ec(u8 reg, int size, long *val)
141{
142	int i;
143	int ret;
144	u8 buffer;
145
146	if (!lock_global_acpi_lock())
147		return -EBUSY;
148
149	*val = 0;
150	for (i = 0; i < size; i++) {
151		ret = ec_read(reg + i, &buffer);
152		if (ret)
153			return ret;
154		*val <<= i * 8;
155		*val += buffer;
156	}
157
158	if (!unlock_global_acpi_lock())
159		return -EBUSY;
160
161	return 0;
162}
163
164static int write_to_ec(u8 reg, u8 value)
165{
166	int ret;
167
168	if (!lock_global_acpi_lock())
169		return -EBUSY;
170
171	ret = ec_write(reg, value);
172
173	if (!unlock_global_acpi_lock())
174		return -EBUSY;
175
176	return ret;
177}
178
179/* Turbo button toggle functions */
180static int tt_toggle_enable(void)
181{
182	u8 reg;
183	u8 val;
184
185	switch (board) {
186	case oxp_mini_amd_a07:
187		reg = OXP_OLD_TURBO_SWITCH_REG;
188		val = OXP_OLD_TURBO_TAKE_VAL;
189		break;
190	case oxp_mini_amd_pro:
191	case aok_zoe_a1:
192		reg = OXP_TURBO_SWITCH_REG;
193		val = OXP_TURBO_TAKE_VAL;
194		break;
195	default:
196		return -EINVAL;
197	}
198	return write_to_ec(reg, val);
199}
200
201static int tt_toggle_disable(void)
202{
203	u8 reg;
204	u8 val;
205
206	switch (board) {
207	case oxp_mini_amd_a07:
208		reg = OXP_OLD_TURBO_SWITCH_REG;
209		val = OXP_OLD_TURBO_RETURN_VAL;
210		break;
211	case oxp_mini_amd_pro:
212	case aok_zoe_a1:
213		reg = OXP_TURBO_SWITCH_REG;
214		val = OXP_TURBO_RETURN_VAL;
215		break;
216	default:
217		return -EINVAL;
218	}
219	return write_to_ec(reg, val);
220}
221
222/* Callbacks for turbo toggle attribute */
223static umode_t tt_toggle_is_visible(struct kobject *kobj,
224				    struct attribute *attr, int n)
225{
226	switch (board) {
227	case aok_zoe_a1:
228	case oxp_mini_amd_a07:
229	case oxp_mini_amd_pro:
230		return attr->mode;
231	default:
232		break;
233	}
234	return 0;
235}
236
237static ssize_t tt_toggle_store(struct device *dev,
238			       struct device_attribute *attr, const char *buf,
239			       size_t count)
240{
241	int rval;
242	bool value;
243
244	rval = kstrtobool(buf, &value);
245	if (rval)
246		return rval;
247
248	if (value) {
249		rval = tt_toggle_enable();
250	} else {
251		rval = tt_toggle_disable();
252	}
253	if (rval)
254		return rval;
255
256	return count;
257}
258
259static ssize_t tt_toggle_show(struct device *dev,
260			      struct device_attribute *attr, char *buf)
261{
262	int retval;
263	u8 reg;
264	long val;
265
266	switch (board) {
267	case oxp_mini_amd_a07:
268		reg = OXP_OLD_TURBO_SWITCH_REG;
269		break;
270	case oxp_mini_amd_pro:
271	case aok_zoe_a1:
272		reg = OXP_TURBO_SWITCH_REG;
273		break;
274	default:
275		return -EINVAL;
276	}
277
278	retval = read_from_ec(reg, 1, &val);
279	if (retval)
280		return retval;
281
282	return sysfs_emit(buf, "%d\n", !!val);
283}
284
285static DEVICE_ATTR_RW(tt_toggle);
286
287/* PWM enable/disable functions */
288static int oxp_pwm_enable(void)
289{
290	return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x01);
291}
292
293static int oxp_pwm_disable(void)
294{
295	return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x00);
296}
297
298/* Callbacks for hwmon interface */
299static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
300				       enum hwmon_sensor_types type, u32 attr, int channel)
301{
302	switch (type) {
303	case hwmon_fan:
304		return 0444;
305	case hwmon_pwm:
306		return 0644;
307	default:
308		return 0;
309	}
310}
311
312static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
313			     u32 attr, int channel, long *val)
314{
315	int ret;
316
317	switch (type) {
318	case hwmon_fan:
319		switch (attr) {
320		case hwmon_fan_input:
321			return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
322		default:
323			break;
324		}
325		break;
326	case hwmon_pwm:
327		switch (attr) {
328		case hwmon_pwm_input:
329			ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
330			if (ret)
331				return ret;
332			switch (board) {
333			case aya_neo_2:
334			case aya_neo_air:
335			case aya_neo_air_pro:
336			case aya_neo_geek:
337			case oxp_mini_amd:
338			case oxp_mini_amd_a07:
339				*val = (*val * 255) / 100;
340				break;
341			case oxp_mini_amd_pro:
342			case aok_zoe_a1:
343			default:
344				break;
345			}
346			return 0;
347		case hwmon_pwm_enable:
348			return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
349		default:
350			break;
351		}
352		break;
353	default:
354		break;
355	}
356	return -EOPNOTSUPP;
357}
358
359static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
360			      u32 attr, int channel, long val)
361{
362	switch (type) {
363	case hwmon_pwm:
364		switch (attr) {
365		case hwmon_pwm_enable:
366			if (val == 1)
367				return oxp_pwm_enable();
368			else if (val == 0)
369				return oxp_pwm_disable();
370			return -EINVAL;
371		case hwmon_pwm_input:
372			if (val < 0 || val > 255)
373				return -EINVAL;
374			switch (board) {
375			case aya_neo_2:
376			case aya_neo_air:
377			case aya_neo_air_pro:
378			case aya_neo_geek:
379			case oxp_mini_amd:
380			case oxp_mini_amd_a07:
381				val = (val * 100) / 255;
382				break;
383			case aok_zoe_a1:
384			case oxp_mini_amd_pro:
385			default:
386				break;
387			}
388			return write_to_ec(OXP_SENSOR_PWM_REG, val);
389		default:
390			break;
391		}
392		break;
393	default:
394		break;
395	}
396	return -EOPNOTSUPP;
397}
398
399/* Known sensors in the OXP EC controllers */
400static const struct hwmon_channel_info * const oxp_platform_sensors[] = {
401	HWMON_CHANNEL_INFO(fan,
402			   HWMON_F_INPUT),
403	HWMON_CHANNEL_INFO(pwm,
404			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
405	NULL,
406};
407
408static struct attribute *oxp_ec_attrs[] = {
409	&dev_attr_tt_toggle.attr,
410	NULL
411};
412
413static struct attribute_group oxp_ec_attribute_group = {
414	.is_visible = tt_toggle_is_visible,
415	.attrs = oxp_ec_attrs,
416};
417
418static const struct attribute_group *oxp_ec_groups[] = {
419	&oxp_ec_attribute_group,
420	NULL
421};
422
423static const struct hwmon_ops oxp_ec_hwmon_ops = {
424	.is_visible = oxp_ec_hwmon_is_visible,
425	.read = oxp_platform_read,
426	.write = oxp_platform_write,
427};
428
429static const struct hwmon_chip_info oxp_ec_chip_info = {
430	.ops = &oxp_ec_hwmon_ops,
431	.info = oxp_platform_sensors,
432};
433
434/* Initialization logic */
435static int oxp_platform_probe(struct platform_device *pdev)
436{
437	struct device *dev = &pdev->dev;
438	struct device *hwdev;
439
440	hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,
441						     &oxp_ec_chip_info, NULL);
442
443	return PTR_ERR_OR_ZERO(hwdev);
444}
445
446static struct platform_driver oxp_platform_driver = {
447	.driver = {
448		.name = "oxp-platform",
449		.dev_groups = oxp_ec_groups,
450	},
451	.probe = oxp_platform_probe,
452};
453
454static struct platform_device *oxp_platform_device;
455
456static int __init oxp_platform_init(void)
457{
458	const struct dmi_system_id *dmi_entry;
459
460	/*
461	 * Have to check for AMD processor here because DMI strings are the
462	 * same between Intel and AMD boards, the only way to tell them apart
463	 * is the CPU.
464	 * Intel boards seem to have different EC registers and values to
465	 * read/write.
466	 */
467	dmi_entry = dmi_first_match(dmi_table);
468	if (!dmi_entry || boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
469		return -ENODEV;
470
471	board = (enum oxp_board)(unsigned long)dmi_entry->driver_data;
472
473	oxp_platform_device =
474		platform_create_bundle(&oxp_platform_driver,
475				       oxp_platform_probe, NULL, 0, NULL, 0);
476
477	return PTR_ERR_OR_ZERO(oxp_platform_device);
478}
479
480static void __exit oxp_platform_exit(void)
481{
482	platform_device_unregister(oxp_platform_device);
483	platform_driver_unregister(&oxp_platform_driver);
484}
485
486MODULE_DEVICE_TABLE(dmi, dmi_table);
487
488module_init(oxp_platform_init);
489module_exit(oxp_platform_exit);
490
491MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
492MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
493MODULE_LICENSE("GPL");