2022-11-04 11:07:00 -03:00
// SPDX-License-Identifier: GPL-2.0+
/*
2022-12-28 18:56:09 -08:00
* Platform driver for OneXPlayer , AOK ZOE , and Aya Neo Handhelds that expose
* fan reading and control via hwmon sysfs .
2022-11-04 11:07:00 -03:00
*
2022-12-28 18:56:09 -08:00
* Old OXP boards have the same DMI strings and they are told apart by
* the boot cpu vendor ( Intel / AMD ) . Currently only AMD boards are
* supported but the code is made to be simple to add other handheld
* boards in the future .
2022-11-25 08:49:01 -03:00
* Fan control is provided via pwm interface in the range [ 0 - 255 ] .
* Old AMD boards use [ 0 - 100 ] as range in the EC , the written value is
* scaled to accommodate for that . Newer boards like the mini PRO and
* AOK ZOE are not scaled but have the same EC layout .
2022-11-04 11:07:00 -03:00
*
* Copyright ( C ) 2022 Joaquín I . Aramendía < samsagax @ gmail . com >
*/
# include <linux/acpi.h>
# include <linux/dmi.h>
# include <linux/hwmon.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/processor.h>
/* Handle ACPI lock mechanism */
static u32 oxp_mutex ;
# define ACPI_LOCK_DELAY_MS 500
static bool lock_global_acpi_lock ( void )
{
return ACPI_SUCCESS ( acpi_acquire_global_lock ( ACPI_LOCK_DELAY_MS , & oxp_mutex ) ) ;
}
static bool unlock_global_acpi_lock ( void )
{
return ACPI_SUCCESS ( acpi_release_global_lock ( oxp_mutex ) ) ;
}
2022-11-25 08:49:01 -03:00
enum oxp_board {
aok_zoe_a1 = 1 ,
2023-04-26 15:44:20 -03:00
aya_neo_2 ,
2022-12-28 18:56:09 -08:00
aya_neo_air ,
2024-02-09 10:01:23 +01:00
aya_neo_air_plus_mendo ,
2022-12-28 18:56:09 -08:00
aya_neo_air_pro ,
2023-04-26 15:44:20 -03:00
aya_neo_geek ,
2022-11-25 08:49:01 -03:00
oxp_mini_amd ,
2023-06-11 11:33:20 -03:00
oxp_mini_amd_a07 ,
2022-11-25 08:49:01 -03:00
oxp_mini_amd_pro ,
} ;
static enum oxp_board board ;
2023-06-11 11:33:20 -03:00
/* Fan reading and PWM */
2022-11-04 11:07:00 -03:00
# define OXP_SENSOR_FAN_REG 0x76 /* Fan reading is 2 registers long */
# define OXP_SENSOR_PWM_ENABLE_REG 0x4A /* PWM enable is 1 register long */
# define OXP_SENSOR_PWM_REG 0x4B /* PWM reading is 1 register long */
2023-06-11 11:33:20 -03:00
/* Turbo button takeover function
* Older boards have different values and EC registers
* for the same function
*/
# define OXP_OLD_TURBO_SWITCH_REG 0x1E
# define OXP_OLD_TURBO_TAKE_VAL 0x01
# define OXP_OLD_TURBO_RETURN_VAL 0x00
# define OXP_TURBO_SWITCH_REG 0xF1
# define OXP_TURBO_TAKE_VAL 0x40
# define OXP_TURBO_RETURN_VAL 0x00
2022-11-04 11:07:00 -03:00
static const struct dmi_system_id dmi_table [ ] = {
2022-11-25 08:49:01 -03:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " AOKZOE " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " AOKZOE A1 AR07 " ) ,
} ,
2023-04-29 11:25:48 -03:00
. driver_data = ( void * ) aok_zoe_a1 ,
2022-11-25 08:49:01 -03:00
} ,
2023-06-24 22:23:44 -03:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " AOKZOE " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " AOKZOE A1 Pro " ) ,
} ,
. driver_data = ( void * ) aok_zoe_a1 ,
} ,
2023-04-26 15:44:20 -03:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " AYANEO " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " AYANEO 2 " ) ,
} ,
2023-04-29 11:25:48 -03:00
. driver_data = ( void * ) aya_neo_2 ,
2023-04-26 15:44:20 -03:00
} ,
2022-12-28 18:56:09 -08:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " AYANEO " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " AIR " ) ,
} ,
2023-04-29 11:25:48 -03:00
. driver_data = ( void * ) aya_neo_air ,
2022-12-28 18:56:09 -08:00
} ,
2024-02-09 10:01:23 +01:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " AYANEO " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " AB05-Mendocino " ) ,
} ,
. driver_data = ( void * ) aya_neo_air_plus_mendo ,
} ,
2022-12-28 18:56:09 -08:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " AYANEO " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " AIR Pro " ) ,
} ,
2023-04-29 11:25:48 -03:00
. driver_data = ( void * ) aya_neo_air_pro ,
2022-12-28 18:56:09 -08:00
} ,
2023-04-26 15:44:20 -03:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " AYANEO " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " GEEK " ) ,
} ,
2023-04-29 11:25:48 -03:00
. driver_data = ( void * ) aya_neo_geek ,
2023-04-26 15:44:20 -03:00
} ,
2022-11-04 11:07:00 -03:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " ONE-NETBOOK " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " ONE XPLAYER " ) ,
} ,
2023-04-29 11:25:48 -03:00
. driver_data = ( void * ) oxp_mini_amd ,
2022-11-25 08:49:01 -03:00
} ,
2023-05-17 15:35:41 -03:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " ONE-NETBOOK " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " ONEXPLAYER mini A07 " ) ,
} ,
2023-06-11 11:33:20 -03:00
. driver_data = ( void * ) oxp_mini_amd_a07 ,
2023-05-17 15:35:41 -03:00
} ,
2022-11-25 08:49:01 -03:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " ONE-NETBOOK " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " ONEXPLAYER Mini Pro " ) ,
} ,
2023-04-29 11:25:48 -03:00
. driver_data = ( void * ) oxp_mini_amd_pro ,
2022-11-04 11:07:00 -03:00
} ,
{ } ,
} ;
/* Helper functions to handle EC read/write */
static int read_from_ec ( u8 reg , int size , long * val )
{
int i ;
int ret ;
u8 buffer ;
if ( ! lock_global_acpi_lock ( ) )
return - EBUSY ;
* val = 0 ;
for ( i = 0 ; i < size ; i + + ) {
ret = ec_read ( reg + i , & buffer ) ;
if ( ret )
return ret ;
* val < < = i * 8 ;
* val + = buffer ;
}
if ( ! unlock_global_acpi_lock ( ) )
return - EBUSY ;
return 0 ;
}
2023-05-25 11:26:52 -03:00
static int write_to_ec ( u8 reg , u8 value )
2022-11-04 11:07:00 -03:00
{
int ret ;
if ( ! lock_global_acpi_lock ( ) )
return - EBUSY ;
ret = ec_write ( reg , value ) ;
if ( ! unlock_global_acpi_lock ( ) )
return - EBUSY ;
return ret ;
}
2023-06-11 11:33:20 -03:00
/* Turbo button toggle functions */
static int tt_toggle_enable ( void )
{
u8 reg ;
u8 val ;
switch ( board ) {
case oxp_mini_amd_a07 :
reg = OXP_OLD_TURBO_SWITCH_REG ;
val = OXP_OLD_TURBO_TAKE_VAL ;
break ;
case oxp_mini_amd_pro :
case aok_zoe_a1 :
reg = OXP_TURBO_SWITCH_REG ;
val = OXP_TURBO_TAKE_VAL ;
break ;
default :
return - EINVAL ;
}
return write_to_ec ( reg , val ) ;
}
static int tt_toggle_disable ( void )
{
u8 reg ;
u8 val ;
switch ( board ) {
case oxp_mini_amd_a07 :
reg = OXP_OLD_TURBO_SWITCH_REG ;
val = OXP_OLD_TURBO_RETURN_VAL ;
break ;
case oxp_mini_amd_pro :
case aok_zoe_a1 :
reg = OXP_TURBO_SWITCH_REG ;
val = OXP_TURBO_RETURN_VAL ;
break ;
default :
return - EINVAL ;
}
return write_to_ec ( reg , val ) ;
}
/* Callbacks for turbo toggle attribute */
2023-07-17 19:25:15 -03:00
static umode_t tt_toggle_is_visible ( struct kobject * kobj ,
struct attribute * attr , int n )
{
switch ( board ) {
case aok_zoe_a1 :
case oxp_mini_amd_a07 :
case oxp_mini_amd_pro :
return attr - > mode ;
default :
break ;
}
return 0 ;
}
2023-06-11 11:33:20 -03:00
static ssize_t tt_toggle_store ( struct device * dev ,
struct device_attribute * attr , const char * buf ,
size_t count )
{
int rval ;
bool value ;
rval = kstrtobool ( buf , & value ) ;
if ( rval )
return rval ;
if ( value ) {
rval = tt_toggle_enable ( ) ;
} else {
rval = tt_toggle_disable ( ) ;
}
2023-06-17 15:11:43 -03:00
if ( rval )
return rval ;
2023-06-11 11:33:20 -03:00
return count ;
}
static ssize_t tt_toggle_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
int retval ;
u8 reg ;
long val ;
switch ( board ) {
case oxp_mini_amd_a07 :
reg = OXP_OLD_TURBO_SWITCH_REG ;
break ;
case oxp_mini_amd_pro :
case aok_zoe_a1 :
reg = OXP_TURBO_SWITCH_REG ;
break ;
default :
return - EINVAL ;
}
retval = read_from_ec ( reg , 1 , & val ) ;
if ( retval )
return retval ;
return sysfs_emit ( buf , " %d \n " , ! ! val ) ;
}
static DEVICE_ATTR_RW ( tt_toggle ) ;
/* PWM enable/disable functions */
2023-05-25 11:26:52 -03:00
static int oxp_pwm_enable ( void )
2022-11-04 11:07:00 -03:00
{
2023-05-25 11:26:52 -03:00
return write_to_ec ( OXP_SENSOR_PWM_ENABLE_REG , 0x01 ) ;
2022-11-04 11:07:00 -03:00
}
2023-05-25 11:26:52 -03:00
static int oxp_pwm_disable ( void )
2022-11-04 11:07:00 -03:00
{
2023-05-25 11:26:52 -03:00
return write_to_ec ( OXP_SENSOR_PWM_ENABLE_REG , 0x00 ) ;
2022-11-04 11:07:00 -03:00
}
/* Callbacks for hwmon interface */
static umode_t oxp_ec_hwmon_is_visible ( const void * drvdata ,
enum hwmon_sensor_types type , u32 attr , int channel )
{
switch ( type ) {
case hwmon_fan :
return 0444 ;
case hwmon_pwm :
return 0644 ;
default :
return 0 ;
}
}
static int oxp_platform_read ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel , long * val )
{
int ret ;
switch ( type ) {
case hwmon_fan :
switch ( attr ) {
case hwmon_fan_input :
return read_from_ec ( OXP_SENSOR_FAN_REG , 2 , val ) ;
default :
break ;
}
break ;
case hwmon_pwm :
switch ( attr ) {
case hwmon_pwm_input :
2022-11-28 15:52:06 -03:00
ret = read_from_ec ( OXP_SENSOR_PWM_REG , 1 , val ) ;
2022-11-04 11:07:00 -03:00
if ( ret )
return ret ;
2022-12-28 18:56:09 -08:00
switch ( board ) {
2023-04-26 15:44:20 -03:00
case aya_neo_2 :
2022-12-28 18:56:09 -08:00
case aya_neo_air :
2024-02-09 10:01:23 +01:00
case aya_neo_air_plus_mendo :
2022-12-28 18:56:09 -08:00
case aya_neo_air_pro :
2023-04-26 15:44:20 -03:00
case aya_neo_geek :
2022-12-28 18:56:09 -08:00
case oxp_mini_amd :
2023-06-11 11:33:20 -03:00
case oxp_mini_amd_a07 :
2022-11-25 08:49:01 -03:00
* val = ( * val * 255 ) / 100 ;
2022-12-28 18:56:09 -08:00
break ;
case oxp_mini_amd_pro :
case aok_zoe_a1 :
default :
break ;
}
2022-11-04 11:07:00 -03:00
return 0 ;
case hwmon_pwm_enable :
return read_from_ec ( OXP_SENSOR_PWM_ENABLE_REG , 1 , val ) ;
default :
break ;
}
break ;
default :
break ;
}
return - EOPNOTSUPP ;
}
static int oxp_platform_write ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel , long val )
{
switch ( type ) {
case hwmon_pwm :
switch ( attr ) {
case hwmon_pwm_enable :
if ( val = = 1 )
2023-05-25 11:26:52 -03:00
return oxp_pwm_enable ( ) ;
2022-11-04 11:07:00 -03:00
else if ( val = = 0 )
2023-05-25 11:26:52 -03:00
return oxp_pwm_disable ( ) ;
2022-11-04 11:07:00 -03:00
return - EINVAL ;
case hwmon_pwm_input :
if ( val < 0 | | val > 255 )
return - EINVAL ;
2022-12-28 18:56:09 -08:00
switch ( board ) {
2023-04-26 15:44:20 -03:00
case aya_neo_2 :
2022-12-28 18:56:09 -08:00
case aya_neo_air :
2024-02-09 10:01:23 +01:00
case aya_neo_air_plus_mendo :
2022-12-28 18:56:09 -08:00
case aya_neo_air_pro :
2023-04-26 15:44:20 -03:00
case aya_neo_geek :
2022-12-28 18:56:09 -08:00
case oxp_mini_amd :
2023-06-11 11:33:20 -03:00
case oxp_mini_amd_a07 :
2022-11-25 08:49:01 -03:00
val = ( val * 100 ) / 255 ;
2022-12-28 18:56:09 -08:00
break ;
case aok_zoe_a1 :
case oxp_mini_amd_pro :
default :
break ;
}
2023-05-25 11:26:52 -03:00
return write_to_ec ( OXP_SENSOR_PWM_REG , val ) ;
2022-11-04 11:07:00 -03:00
default :
break ;
}
break ;
default :
break ;
}
return - EOPNOTSUPP ;
}
/* Known sensors in the OXP EC controllers */
2023-04-06 22:35:30 +02:00
static const struct hwmon_channel_info * const oxp_platform_sensors [ ] = {
2022-11-04 11:07:00 -03:00
HWMON_CHANNEL_INFO ( fan ,
HWMON_F_INPUT ) ,
HWMON_CHANNEL_INFO ( pwm ,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE ) ,
NULL ,
} ;
2023-06-11 11:33:20 -03:00
static struct attribute * oxp_ec_attrs [ ] = {
& dev_attr_tt_toggle . attr ,
NULL
} ;
2023-07-17 19:25:15 -03:00
static struct attribute_group oxp_ec_attribute_group = {
. is_visible = tt_toggle_is_visible ,
. attrs = oxp_ec_attrs ,
} ;
static const struct attribute_group * oxp_ec_groups [ ] = {
& oxp_ec_attribute_group ,
NULL
} ;
2023-06-11 11:33:20 -03:00
2022-11-04 11:07:00 -03:00
static const struct hwmon_ops oxp_ec_hwmon_ops = {
. is_visible = oxp_ec_hwmon_is_visible ,
. read = oxp_platform_read ,
. write = oxp_platform_write ,
} ;
static const struct hwmon_chip_info oxp_ec_chip_info = {
. ops = & oxp_ec_hwmon_ops ,
. info = oxp_platform_sensors ,
} ;
/* Initialization logic */
static int oxp_platform_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device * hwdev ;
hwdev = devm_hwmon_device_register_with_info ( dev , " oxpec " , NULL ,
& oxp_ec_chip_info , NULL ) ;
return PTR_ERR_OR_ZERO ( hwdev ) ;
}
static struct platform_driver oxp_platform_driver = {
. driver = {
. name = " oxp-platform " ,
2023-07-17 19:25:15 -03:00
. dev_groups = oxp_ec_groups ,
2022-11-04 11:07:00 -03:00
} ,
. probe = oxp_platform_probe ,
} ;
static struct platform_device * oxp_platform_device ;
static int __init oxp_platform_init ( void )
{
2023-07-17 19:25:16 -03:00
const struct dmi_system_id * dmi_entry ;
/*
* Have to check for AMD processor here because DMI strings are the
* same between Intel and AMD boards , the only way to tell them apart
* is the CPU .
* Intel boards seem to have different EC registers and values to
* read / write .
*/
dmi_entry = dmi_first_match ( dmi_table ) ;
if ( ! dmi_entry | | boot_cpu_data . x86_vendor ! = X86_VENDOR_AMD )
return - ENODEV ;
board = ( enum oxp_board ) ( unsigned long ) dmi_entry - > driver_data ;
2022-11-04 11:07:00 -03:00
oxp_platform_device =
platform_create_bundle ( & oxp_platform_driver ,
oxp_platform_probe , NULL , 0 , NULL , 0 ) ;
return PTR_ERR_OR_ZERO ( oxp_platform_device ) ;
}
static void __exit oxp_platform_exit ( void )
{
platform_device_unregister ( oxp_platform_device ) ;
platform_driver_unregister ( & oxp_platform_driver ) ;
}
MODULE_DEVICE_TABLE ( dmi , dmi_table ) ;
module_init ( oxp_platform_init ) ;
module_exit ( oxp_platform_exit ) ;
MODULE_AUTHOR ( " Joaquín Ignacio Aramendía <samsagax@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Platform driver that handles EC sensors of OneXPlayer devices " ) ;
MODULE_LICENSE ( " GPL " ) ;