Linux Audio

Check our new training course

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