2016-05-09 15:54:58 +08:00
/*
* INT3406 thermal driver for display participant device
*
* Copyright ( C ) 2016 , Intel Corporation
* Authors : Aaron Lu < aaron . lu @ intel . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
*/
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/acpi.h>
# include <linux/backlight.h>
# include <linux/thermal.h>
# include <acpi/video.h>
# define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80
struct int3406_thermal_data {
int upper_limit ;
int lower_limit ;
acpi_handle handle ;
struct acpi_video_device_brightness * br ;
struct backlight_device * raw_bd ;
struct thermal_cooling_device * cooling_dev ;
} ;
2017-07-20 10:22:47 +08:00
/*
* According to the ACPI spec ,
* " Each brightness level is represented by a number between 0 and 100,
* and can be thought of as a percentage . For example , 50 can be 50 %
* power consumption or 50 % brightness , as defined by the OEM . "
*
* As int3406 device uses this value to communicate with the native
* graphics driver , we make the assumption that it represents
* the percentage of brightness only
*/
# define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100)
# define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness)
2016-05-09 15:54:58 +08:00
static int
int3406_thermal_get_max_state ( struct thermal_cooling_device * cooling_dev ,
unsigned long * state )
{
struct int3406_thermal_data * d = cooling_dev - > devdata ;
2017-07-20 10:22:47 +08:00
* state = d - > upper_limit - d - > lower_limit ;
2016-05-09 15:54:58 +08:00
return 0 ;
}
static int
int3406_thermal_set_cur_state ( struct thermal_cooling_device * cooling_dev ,
unsigned long state )
{
struct int3406_thermal_data * d = cooling_dev - > devdata ;
2017-07-20 10:22:47 +08:00
int acpi_level , raw_level ;
2016-05-09 15:54:58 +08:00
2017-07-20 10:22:47 +08:00
if ( state > d - > upper_limit - d - > lower_limit )
2016-05-09 15:54:58 +08:00
return - EINVAL ;
2017-07-20 10:22:47 +08:00
acpi_level = d - > br - > levels [ d - > upper_limit - state ] ;
2016-05-09 15:54:58 +08:00
2017-07-20 10:22:47 +08:00
raw_level = ACPI_TO_RAW ( acpi_level , d ) ;
2016-05-09 15:54:58 +08:00
return backlight_device_set_brightness ( d - > raw_bd , raw_level ) ;
}
static int
int3406_thermal_get_cur_state ( struct thermal_cooling_device * cooling_dev ,
unsigned long * state )
{
struct int3406_thermal_data * d = cooling_dev - > devdata ;
2017-07-20 10:22:47 +08:00
int acpi_level ;
int index ;
2016-05-09 15:54:58 +08:00
2017-07-20 10:22:47 +08:00
acpi_level = RAW_TO_ACPI ( d - > raw_bd - > props . brightness , d ) ;
2016-05-09 15:54:58 +08:00
/*
2017-07-20 10:22:47 +08:00
* There is no 1 : 1 mapping between the firmware interface level
* with the raw interface level , we will have to find one that is
* right above it .
2016-05-09 15:54:58 +08:00
*/
2017-07-20 10:22:47 +08:00
for ( index = d - > lower_limit ; index < d - > upper_limit ; index + + ) {
if ( acpi_level < = d - > br - > levels [ index ] )
2016-05-09 15:54:58 +08:00
break ;
}
2017-07-20 10:22:47 +08:00
* state = d - > upper_limit - index ;
2016-05-09 15:54:58 +08:00
return 0 ;
}
static const struct thermal_cooling_device_ops video_cooling_ops = {
. get_max_state = int3406_thermal_get_max_state ,
. get_cur_state = int3406_thermal_get_cur_state ,
. set_cur_state = int3406_thermal_set_cur_state ,
} ;
static int int3406_thermal_get_index ( int * array , int nr , int value )
{
int i ;
2017-07-20 10:22:47 +08:00
for ( i = 2 ; i < nr ; i + + ) {
2016-05-09 15:54:58 +08:00
if ( array [ i ] = = value )
break ;
}
return i = = nr ? - ENOENT : i ;
}
static void int3406_thermal_get_limit ( struct int3406_thermal_data * d )
{
acpi_status status ;
unsigned long long lower_limit , upper_limit ;
status = acpi_evaluate_integer ( d - > handle , " DDDL " , NULL , & lower_limit ) ;
2017-07-20 10:22:47 +08:00
if ( ACPI_SUCCESS ( status ) )
d - > lower_limit = int3406_thermal_get_index ( d - > br - > levels ,
d - > br - > count , lower_limit ) ;
2016-05-09 15:54:58 +08:00
status = acpi_evaluate_integer ( d - > handle , " DDPC " , NULL , & upper_limit ) ;
2017-07-20 10:22:47 +08:00
if ( ACPI_SUCCESS ( status ) )
d - > upper_limit = int3406_thermal_get_index ( d - > br - > levels ,
d - > br - > count , upper_limit ) ;
/* lower_limit and upper_limit should be always set */
d - > lower_limit = d - > lower_limit > 0 ? d - > lower_limit : 2 ;
d - > upper_limit = d - > upper_limit > 0 ? d - > upper_limit : d - > br - > count - 1 ;
2016-05-09 15:54:58 +08:00
}
static void int3406_notify ( acpi_handle handle , u32 event , void * data )
{
if ( event = = INT3406_BRIGHTNESS_LIMITS_CHANGED )
int3406_thermal_get_limit ( data ) ;
}
static int int3406_thermal_probe ( struct platform_device * pdev )
{
struct acpi_device * adev = ACPI_COMPANION ( & pdev - > dev ) ;
struct int3406_thermal_data * d ;
struct backlight_device * bd ;
int ret ;
if ( ! ACPI_HANDLE ( & pdev - > dev ) )
return - ENODEV ;
d = devm_kzalloc ( & pdev - > dev , sizeof ( * d ) , GFP_KERNEL ) ;
if ( ! d )
return - ENOMEM ;
d - > handle = ACPI_HANDLE ( & pdev - > dev ) ;
bd = backlight_device_get_by_type ( BACKLIGHT_RAW ) ;
if ( ! bd )
return - ENODEV ;
d - > raw_bd = bd ;
2016-05-21 15:30:46 +08:00
ret = acpi_video_get_levels ( ACPI_COMPANION ( & pdev - > dev ) , & d - > br , NULL ) ;
2016-05-09 15:54:58 +08:00
if ( ret )
return ret ;
int3406_thermal_get_limit ( d ) ;
d - > cooling_dev = thermal_cooling_device_register ( acpi_device_bid ( adev ) ,
d , & video_cooling_ops ) ;
if ( IS_ERR ( d - > cooling_dev ) )
goto err ;
ret = acpi_install_notify_handler ( adev - > handle , ACPI_DEVICE_NOTIFY ,
int3406_notify , d ) ;
if ( ret )
goto err_cdev ;
platform_set_drvdata ( pdev , d ) ;
return 0 ;
err_cdev :
thermal_cooling_device_unregister ( d - > cooling_dev ) ;
err :
kfree ( d - > br ) ;
return - ENODEV ;
}
static int int3406_thermal_remove ( struct platform_device * pdev )
{
struct int3406_thermal_data * d = platform_get_drvdata ( pdev ) ;
thermal_cooling_device_unregister ( d - > cooling_dev ) ;
kfree ( d - > br ) ;
return 0 ;
}
static const struct acpi_device_id int3406_thermal_match [ ] = {
{ " INT3406 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , int3406_thermal_match ) ;
static struct platform_driver int3406_thermal_driver = {
. probe = int3406_thermal_probe ,
. remove = int3406_thermal_remove ,
. driver = {
. name = " int3406 thermal " ,
. acpi_match_table = int3406_thermal_match ,
} ,
} ;
module_platform_driver ( int3406_thermal_driver ) ;
MODULE_DESCRIPTION ( " INT3406 Thermal driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;