2019-05-27 08:55:21 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2014-11-24 17:21:54 +08:00
/*
* intel_pmic . c - Intel PMIC operation region driver
*
* Copyright ( C ) 2014 Intel Corporation . All rights reserved .
*/
2016-07-11 18:05:16 -04:00
# include <linux/export.h>
2014-11-24 17:21:54 +08:00
# include <linux/acpi.h>
2019-01-07 12:15:53 +01:00
# include <linux/mfd/intel_soc_pmic.h>
2014-11-24 17:21:54 +08:00
# include <linux/regmap.h>
2015-01-28 11:56:47 -08:00
# include <acpi/acpi_lpat.h>
2014-11-24 17:21:54 +08:00
# include "intel_pmic.h"
# define PMIC_POWER_OPREGION_ID 0x8d
# define PMIC_THERMAL_OPREGION_ID 0x8c
2016-06-22 17:55:39 +03:00
# define PMIC_REGS_OPREGION_ID 0x8f
struct intel_pmic_regs_handler_ctx {
unsigned int val ;
u16 addr ;
} ;
2014-11-24 17:21:54 +08:00
struct intel_pmic_opregion {
struct mutex lock ;
2015-01-28 11:56:47 -08:00
struct acpi_lpat_conversion_table * lpat_table ;
2014-11-24 17:21:54 +08:00
struct regmap * regmap ;
struct intel_pmic_opregion_data * data ;
2016-06-22 17:55:39 +03:00
struct intel_pmic_regs_handler_ctx ctx ;
2014-11-24 17:21:54 +08:00
} ;
2019-01-07 12:15:53 +01:00
static struct intel_pmic_opregion * intel_pmic_opregion ;
2014-11-24 17:21:54 +08:00
static int pmic_get_reg_bit ( int address , struct pmic_table * table ,
int count , int * reg , int * bit )
{
int i ;
for ( i = 0 ; i < count ; i + + ) {
if ( table [ i ] . address = = address ) {
* reg = table [ i ] . reg ;
if ( bit )
* bit = table [ i ] . bit ;
return 0 ;
}
}
return - ENOENT ;
}
static acpi_status intel_pmic_power_handler ( u32 function ,
acpi_physical_address address , u32 bits , u64 * value64 ,
void * handler_context , void * region_context )
{
struct intel_pmic_opregion * opregion = region_context ;
struct regmap * regmap = opregion - > regmap ;
struct intel_pmic_opregion_data * d = opregion - > data ;
int reg , bit , result ;
if ( bits ! = 32 | | ! value64 )
return AE_BAD_PARAMETER ;
if ( function = = ACPI_WRITE & & ! ( * value64 = = 0 | | * value64 = = 1 ) )
return AE_BAD_PARAMETER ;
result = pmic_get_reg_bit ( address , d - > power_table ,
d - > power_table_count , & reg , & bit ) ;
if ( result = = - ENOENT )
return AE_BAD_PARAMETER ;
mutex_lock ( & opregion - > lock ) ;
result = function = = ACPI_READ ?
d - > get_power ( regmap , reg , bit , value64 ) :
d - > update_power ( regmap , reg , bit , * value64 = = 1 ) ;
mutex_unlock ( & opregion - > lock ) ;
return result ? AE_ERROR : AE_OK ;
}
static int pmic_read_temp ( struct intel_pmic_opregion * opregion ,
int reg , u64 * value )
{
int raw_temp , temp ;
if ( ! opregion - > data - > get_raw_temp )
return - ENXIO ;
raw_temp = opregion - > data - > get_raw_temp ( opregion - > regmap , reg ) ;
if ( raw_temp < 0 )
return raw_temp ;
2015-01-28 11:56:47 -08:00
if ( ! opregion - > lpat_table ) {
2014-11-24 17:21:54 +08:00
* value = raw_temp ;
return 0 ;
}
2015-01-28 11:56:47 -08:00
temp = acpi_lpat_raw_to_temp ( opregion - > lpat_table , raw_temp ) ;
2014-11-24 17:21:54 +08:00
if ( temp < 0 )
return temp ;
* value = temp ;
return 0 ;
}
static int pmic_thermal_temp ( struct intel_pmic_opregion * opregion , int reg ,
u32 function , u64 * value )
{
return function = = ACPI_READ ?
pmic_read_temp ( opregion , reg , value ) : - EINVAL ;
}
static int pmic_thermal_aux ( struct intel_pmic_opregion * opregion , int reg ,
u32 function , u64 * value )
{
int raw_temp ;
if ( function = = ACPI_READ )
return pmic_read_temp ( opregion , reg , value ) ;
if ( ! opregion - > data - > update_aux )
return - ENXIO ;
2015-01-28 11:56:47 -08:00
if ( opregion - > lpat_table ) {
raw_temp = acpi_lpat_temp_to_raw ( opregion - > lpat_table , * value ) ;
2014-11-24 17:21:54 +08:00
if ( raw_temp < 0 )
return raw_temp ;
} else {
raw_temp = * value ;
}
return opregion - > data - > update_aux ( opregion - > regmap , reg , raw_temp ) ;
}
static int pmic_thermal_pen ( struct intel_pmic_opregion * opregion , int reg ,
2016-06-23 17:45:35 -07:00
int bit , u32 function , u64 * value )
2014-11-24 17:21:54 +08:00
{
struct intel_pmic_opregion_data * d = opregion - > data ;
struct regmap * regmap = opregion - > regmap ;
if ( ! d - > get_policy | | ! d - > update_policy )
return - ENXIO ;
if ( function = = ACPI_READ )
2016-06-23 17:45:35 -07:00
return d - > get_policy ( regmap , reg , bit , value ) ;
2014-11-24 17:21:54 +08:00
if ( * value ! = 0 & & * value ! = 1 )
return - EINVAL ;
2016-06-23 17:45:35 -07:00
return d - > update_policy ( regmap , reg , bit , * value ) ;
2014-11-24 17:21:54 +08:00
}
static bool pmic_thermal_is_temp ( int address )
{
return ( address < = 0x3c ) & & ! ( address % 12 ) ;
}
static bool pmic_thermal_is_aux ( int address )
{
return ( address > = 4 & & address < = 0x40 & & ! ( ( address - 4 ) % 12 ) ) | |
( address > = 8 & & address < = 0x44 & & ! ( ( address - 8 ) % 12 ) ) ;
}
static bool pmic_thermal_is_pen ( int address )
{
return address > = 0x48 & & address < = 0x5c ;
}
static acpi_status intel_pmic_thermal_handler ( u32 function ,
acpi_physical_address address , u32 bits , u64 * value64 ,
void * handler_context , void * region_context )
{
struct intel_pmic_opregion * opregion = region_context ;
struct intel_pmic_opregion_data * d = opregion - > data ;
2016-06-23 17:45:35 -07:00
int reg , bit , result ;
2014-11-24 17:21:54 +08:00
if ( bits ! = 32 | | ! value64 )
return AE_BAD_PARAMETER ;
result = pmic_get_reg_bit ( address , d - > thermal_table ,
2016-06-23 17:45:35 -07:00
d - > thermal_table_count , & reg , & bit ) ;
2014-11-24 17:21:54 +08:00
if ( result = = - ENOENT )
return AE_BAD_PARAMETER ;
mutex_lock ( & opregion - > lock ) ;
if ( pmic_thermal_is_temp ( address ) )
result = pmic_thermal_temp ( opregion , reg , function , value64 ) ;
else if ( pmic_thermal_is_aux ( address ) )
result = pmic_thermal_aux ( opregion , reg , function , value64 ) ;
else if ( pmic_thermal_is_pen ( address ) )
2016-06-23 17:45:35 -07:00
result = pmic_thermal_pen ( opregion , reg , bit ,
function , value64 ) ;
2014-11-24 17:21:54 +08:00
else
result = - EINVAL ;
mutex_unlock ( & opregion - > lock ) ;
if ( result < 0 ) {
if ( result = = - EINVAL )
return AE_BAD_PARAMETER ;
else
return AE_ERROR ;
}
return AE_OK ;
}
2016-06-22 17:55:39 +03:00
static acpi_status intel_pmic_regs_handler ( u32 function ,
acpi_physical_address address , u32 bits , u64 * value64 ,
void * handler_context , void * region_context )
{
struct intel_pmic_opregion * opregion = region_context ;
ACPI: PMIC: Fix intel_pmic_regs_handler() read accesses
The handling of PMIC register reads through writing 0 to address 4
of the OpRegion is wrong. Instead of returning the read value
through the value64, which is a no-op for function == ACPI_WRITE calls,
store the value and then on a subsequent function == ACPI_READ with
address == 3 (the address for the value field of the OpRegion)
return the stored value.
This has been tested on a Xiaomi Mi Pad 2 and makes the ACPI battery dev
there mostly functional (unfortunately there are still other issues).
Here are the SET() / GET() functions of the PMIC ACPI device,
which use this OpRegion, which clearly show the new behavior to
be correct:
OperationRegion (REGS, 0x8F, Zero, 0x50)
Field (REGS, ByteAcc, NoLock, Preserve)
{
CLNT, 8,
SA, 8,
OFF, 8,
VAL, 8,
RWM, 8
}
Method (GET, 3, Serialized)
{
If ((AVBE == One))
{
CLNT = Arg0
SA = Arg1
OFF = Arg2
RWM = Zero
If ((AVBG == One))
{
GPRW = Zero
}
}
Return (VAL) /* \_SB_.PCI0.I2C7.PMI5.VAL_ */
}
Method (SET, 4, Serialized)
{
If ((AVBE == One))
{
CLNT = Arg0
SA = Arg1
OFF = Arg2
VAL = Arg3
RWM = One
If ((AVBG == One))
{
GPRW = One
}
}
}
Fixes: 0afa877a5650 ("ACPI / PMIC: intel: add REGS operation region support")
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2021-10-31 16:31:35 +01:00
int result = - EINVAL ;
if ( function = = ACPI_WRITE ) {
switch ( address ) {
case 0 :
return AE_OK ;
case 1 :
opregion - > ctx . addr | = ( * value64 & 0xff ) < < 8 ;
return AE_OK ;
case 2 :
opregion - > ctx . addr | = * value64 & 0xff ;
return AE_OK ;
case 3 :
opregion - > ctx . val = * value64 & 0xff ;
return AE_OK ;
case 4 :
if ( * value64 ) {
result = regmap_write ( opregion - > regmap , opregion - > ctx . addr ,
opregion - > ctx . val ) ;
} else {
result = regmap_read ( opregion - > regmap , opregion - > ctx . addr ,
& opregion - > ctx . val ) ;
}
opregion - > ctx . addr = 0 ;
}
}
2016-06-22 17:55:39 +03:00
ACPI: PMIC: Fix intel_pmic_regs_handler() read accesses
The handling of PMIC register reads through writing 0 to address 4
of the OpRegion is wrong. Instead of returning the read value
through the value64, which is a no-op for function == ACPI_WRITE calls,
store the value and then on a subsequent function == ACPI_READ with
address == 3 (the address for the value field of the OpRegion)
return the stored value.
This has been tested on a Xiaomi Mi Pad 2 and makes the ACPI battery dev
there mostly functional (unfortunately there are still other issues).
Here are the SET() / GET() functions of the PMIC ACPI device,
which use this OpRegion, which clearly show the new behavior to
be correct:
OperationRegion (REGS, 0x8F, Zero, 0x50)
Field (REGS, ByteAcc, NoLock, Preserve)
{
CLNT, 8,
SA, 8,
OFF, 8,
VAL, 8,
RWM, 8
}
Method (GET, 3, Serialized)
{
If ((AVBE == One))
{
CLNT = Arg0
SA = Arg1
OFF = Arg2
RWM = Zero
If ((AVBG == One))
{
GPRW = Zero
}
}
Return (VAL) /* \_SB_.PCI0.I2C7.PMI5.VAL_ */
}
Method (SET, 4, Serialized)
{
If ((AVBE == One))
{
CLNT = Arg0
SA = Arg1
OFF = Arg2
VAL = Arg3
RWM = One
If ((AVBG == One))
{
GPRW = One
}
}
}
Fixes: 0afa877a5650 ("ACPI / PMIC: intel: add REGS operation region support")
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2021-10-31 16:31:35 +01:00
if ( function = = ACPI_READ & & address = = 3 ) {
* value64 = opregion - > ctx . val ;
2016-06-22 17:55:39 +03:00
return AE_OK ;
}
if ( result < 0 ) {
if ( result = = - EINVAL )
return AE_BAD_PARAMETER ;
else
return AE_ERROR ;
}
return AE_OK ;
}
2014-11-24 17:21:54 +08:00
int intel_pmic_install_opregion_handler ( struct device * dev , acpi_handle handle ,
struct regmap * regmap ,
struct intel_pmic_opregion_data * d )
{
2019-10-24 23:38:24 +02:00
acpi_status status = AE_OK ;
2014-11-24 17:21:54 +08:00
struct intel_pmic_opregion * opregion ;
2015-01-28 11:56:47 -08:00
int ret ;
2014-11-24 17:21:54 +08:00
if ( ! dev | | ! regmap | | ! d )
return - EINVAL ;
if ( ! handle )
return - ENODEV ;
opregion = devm_kzalloc ( dev , sizeof ( * opregion ) , GFP_KERNEL ) ;
if ( ! opregion )
return - ENOMEM ;
mutex_init ( & opregion - > lock ) ;
opregion - > regmap = regmap ;
2015-01-28 11:56:47 -08:00
opregion - > lpat_table = acpi_lpat_get_conversion_table ( handle ) ;
2014-11-24 17:21:54 +08:00
2019-10-24 23:38:24 +02:00
if ( d - > power_table_count )
status = acpi_install_address_space_handler ( handle ,
2014-11-24 17:21:54 +08:00
PMIC_POWER_OPREGION_ID ,
intel_pmic_power_handler ,
NULL , opregion ) ;
2015-01-28 11:56:47 -08:00
if ( ACPI_FAILURE ( status ) ) {
ret = - ENODEV ;
goto out_error ;
}
2014-11-24 17:21:54 +08:00
2019-10-24 23:38:24 +02:00
if ( d - > thermal_table_count )
status = acpi_install_address_space_handler ( handle ,
2014-11-24 17:21:54 +08:00
PMIC_THERMAL_OPREGION_ID ,
intel_pmic_thermal_handler ,
NULL , opregion ) ;
if ( ACPI_FAILURE ( status ) ) {
2015-01-28 11:56:47 -08:00
ret = - ENODEV ;
2016-06-22 17:55:39 +03:00
goto out_remove_power_handler ;
}
status = acpi_install_address_space_handler ( handle ,
PMIC_REGS_OPREGION_ID , intel_pmic_regs_handler , NULL ,
opregion ) ;
if ( ACPI_FAILURE ( status ) ) {
ret = - ENODEV ;
goto out_remove_thermal_handler ;
2014-11-24 17:21:54 +08:00
}
opregion - > data = d ;
2019-01-07 12:15:53 +01:00
intel_pmic_opregion = opregion ;
2014-11-24 17:21:54 +08:00
return 0 ;
2015-01-28 11:56:47 -08:00
2016-06-22 17:55:39 +03:00
out_remove_thermal_handler :
2019-10-24 23:38:24 +02:00
if ( d - > thermal_table_count )
acpi_remove_address_space_handler ( handle ,
PMIC_THERMAL_OPREGION_ID ,
intel_pmic_thermal_handler ) ;
2016-06-22 17:55:39 +03:00
out_remove_power_handler :
2019-10-24 23:38:24 +02:00
if ( d - > power_table_count )
acpi_remove_address_space_handler ( handle ,
PMIC_POWER_OPREGION_ID ,
intel_pmic_power_handler ) ;
2016-06-22 17:55:39 +03:00
2015-01-28 11:56:47 -08:00
out_error :
acpi_lpat_free_conversion_table ( opregion - > lpat_table ) ;
return ret ;
2014-11-24 17:21:54 +08:00
}
EXPORT_SYMBOL_GPL ( intel_pmic_install_opregion_handler ) ;
2019-01-07 12:15:53 +01:00
/**
* intel_soc_pmic_exec_mipi_pmic_seq_element - Execute PMIC MIPI sequence
* @ i2c_address : I2C client address for the PMIC
* @ reg_address : PMIC register address
* @ value : New value for the register bits to change
* @ mask : Mask indicating which register bits to change
*
* DSI LCD panels describe an initialization sequence in the i915 VBT ( Video
* BIOS Tables ) using so called MIPI sequences . One possible element in these
* sequences is a PMIC specific element of 15 bytes .
*
* This function executes these PMIC specific elements sending the embedded
* commands to the PMIC .
*
* Return 0 on success , < 0 on failure .
*/
int intel_soc_pmic_exec_mipi_pmic_seq_element ( u16 i2c_address , u32 reg_address ,
u32 value , u32 mask )
{
struct intel_pmic_opregion_data * d ;
int ret ;
if ( ! intel_pmic_opregion ) {
pr_warn ( " %s: No PMIC registered \n " , __func__ ) ;
return - ENXIO ;
}
d = intel_pmic_opregion - > data ;
mutex_lock ( & intel_pmic_opregion - > lock ) ;
if ( d - > exec_mipi_pmic_seq_element ) {
ret = d - > exec_mipi_pmic_seq_element ( intel_pmic_opregion - > regmap ,
i2c_address , reg_address ,
value , mask ) ;
2019-01-07 12:15:55 +01:00
} else if ( d - > pmic_i2c_address ) {
if ( i2c_address = = d - > pmic_i2c_address ) {
ret = regmap_update_bits ( intel_pmic_opregion - > regmap ,
reg_address , mask , value ) ;
} else {
pr_err ( " %s: Unexpected i2c-addr: 0x%02x (reg-addr 0x%x value 0x%x mask 0x%x) \n " ,
__func__ , i2c_address , reg_address , value , mask ) ;
ret = - ENXIO ;
}
2019-01-07 12:15:53 +01:00
} else {
pr_warn ( " %s: Not implemented \n " , __func__ ) ;
pr_warn ( " %s: i2c-addr: 0x%x reg-addr 0x%x value 0x%x mask 0x%x \n " ,
__func__ , i2c_address , reg_address , value , mask ) ;
ret = - EOPNOTSUPP ;
}
mutex_unlock ( & intel_pmic_opregion - > lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( intel_soc_pmic_exec_mipi_pmic_seq_element ) ;