2022-11-04 17:07:00 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Platform driver for OXP Handhelds that expose fan reading and control
* via hwmon sysfs .
*
2022-11-25 14:49:01 +03:00
* Old boards have the same DMI strings and they are told appart by the
2022-11-04 17:07:00 +03:00
* 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 14: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 17:07:00 +03:00
*
* Copyright ( C ) 2022 Joaquín I . Aramendía < samsagax @ gmail . com >
*/
# include <linux/acpi.h>
# include <linux/dev_printk.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 14:49:01 +03:00
enum oxp_board {
aok_zoe_a1 = 1 ,
oxp_mini_amd ,
oxp_mini_amd_pro ,
} ;
static enum oxp_board board ;
2022-11-04 17: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 */
static const struct dmi_system_id dmi_table [ ] = {
2022-11-25 14:49:01 +03:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " AOKZOE " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " AOKZOE A1 AR07 " ) ,
} ,
. driver_data = ( void * ) & ( enum oxp_board ) { aok_zoe_a1 } ,
} ,
2022-11-04 17:07:00 +03:00
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " ONE-NETBOOK " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " ONE XPLAYER " ) ,
} ,
2022-11-25 14:49:01 +03:00
. driver_data = ( void * ) & ( enum oxp_board ) { oxp_mini_amd } ,
} ,
{
. matches = {
DMI_MATCH ( DMI_BOARD_VENDOR , " ONE-NETBOOK " ) ,
DMI_EXACT_MATCH ( DMI_BOARD_NAME , " ONEXPLAYER Mini Pro " ) ,
} ,
. driver_data = ( void * ) & ( enum oxp_board ) { oxp_mini_amd_pro } ,
2022-11-04 17: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 ;
}
static int write_to_ec ( const struct device * dev , u8 reg , u8 value )
{
int ret ;
if ( ! lock_global_acpi_lock ( ) )
return - EBUSY ;
ret = ec_write ( reg , value ) ;
if ( ! unlock_global_acpi_lock ( ) )
return - EBUSY ;
return ret ;
}
static int oxp_pwm_enable ( const struct device * dev )
{
return write_to_ec ( dev , OXP_SENSOR_PWM_ENABLE_REG , 0x01 ) ;
}
static int oxp_pwm_disable ( const struct device * dev )
{
return write_to_ec ( dev , OXP_SENSOR_PWM_ENABLE_REG , 0x00 ) ;
}
/* 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 21:52:06 +03:00
ret = read_from_ec ( OXP_SENSOR_PWM_REG , 1 , val ) ;
2022-11-04 17:07:00 +03:00
if ( ret )
return ret ;
2022-11-25 14:49:01 +03:00
if ( board = = oxp_mini_amd )
* val = ( * val * 255 ) / 100 ;
2022-11-04 17: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 )
return oxp_pwm_enable ( dev ) ;
else if ( val = = 0 )
return oxp_pwm_disable ( dev ) ;
return - EINVAL ;
case hwmon_pwm_input :
if ( val < 0 | | val > 255 )
return - EINVAL ;
2022-11-25 14:49:01 +03:00
if ( board = = oxp_mini_amd )
val = ( val * 100 ) / 255 ;
2022-11-04 17:07:00 +03:00
return write_to_ec ( dev , OXP_SENSOR_PWM_REG , val ) ;
default :
break ;
}
break ;
default :
break ;
}
return - EOPNOTSUPP ;
}
/* Known sensors in the OXP EC controllers */
static const struct hwmon_channel_info * oxp_platform_sensors [ ] = {
HWMON_CHANNEL_INFO ( fan ,
HWMON_F_INPUT ) ,
HWMON_CHANNEL_INFO ( pwm ,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE ) ,
NULL ,
} ;
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 )
{
const struct dmi_system_id * dmi_entry ;
struct device * dev = & pdev - > dev ;
struct device * hwdev ;
/*
* Have to check for AMD processor here because DMI strings are the
* same between Intel and AMD boards , the only way to tell them appart
* 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 ;
2022-11-25 14:49:01 +03:00
board = * ( ( enum oxp_board * ) dmi_entry - > driver_data ) ;
2022-11-04 17:07:00 +03:00
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 " ,
} ,
. probe = oxp_platform_probe ,
} ;
static struct platform_device * oxp_platform_device ;
static int __init oxp_platform_init ( void )
{
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 " ) ;