2019-05-27 09:55:06 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2017-03-28 17:43:33 +03:00
/*
* Thermal device driver for DA9062 and DA9061
* Copyright ( C ) 2017 Dialog Semiconductor
*/
/* When over-temperature is reached, an interrupt from the device will be
* triggered . Following this event the interrupt will be disabled and
* periodic transmission of uevents ( HOT trip point ) should define the
* first level of temperature supervision . It is expected that any final
* implementation of the thermal driver will include a . notify ( ) function
* to implement these uevents to userspace .
*
* These uevents are intended to indicate non - invasive temperature control
* of the system , where the necessary measures for cooling are the
* responsibility of the host software . Once the temperature falls again ,
* the IRQ is re - enabled so the start of a new over - temperature event can
* be detected without constant software monitoring .
*/
# include <linux/errno.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/thermal.h>
# include <linux/workqueue.h>
# include <linux/mfd/da9062/core.h>
# include <linux/mfd/da9062/registers.h>
/* Minimum, maximum and default polling millisecond periods are provided
* here as an example . It is expected that any final implementation to also
* include a modification of these settings to match the required
* application .
*/
# define DA9062_DEFAULT_POLLING_MS_PERIOD 3000
# define DA9062_MAX_POLLING_MS_PERIOD 10000
# define DA9062_MIN_POLLING_MS_PERIOD 1000
# define DA9062_MILLI_CELSIUS(t) ((t) * 1000)
struct da9062_thermal_config {
const char * name ;
} ;
struct da9062_thermal {
struct da9062 * hw ;
struct delayed_work work ;
struct thermal_zone_device * zone ;
struct mutex lock ; /* protection for da9062_thermal temperature */
int temperature ;
int irq ;
const struct da9062_thermal_config * config ;
struct device * dev ;
} ;
static void da9062_thermal_poll_on ( struct work_struct * work )
{
struct da9062_thermal * thermal = container_of ( work ,
struct da9062_thermal ,
work . work ) ;
unsigned long delay ;
unsigned int val ;
int ret ;
/* clear E_TEMP */
ret = regmap_write ( thermal - > hw - > regmap ,
DA9062AA_EVENT_B ,
DA9062AA_E_TEMP_MASK ) ;
if ( ret < 0 ) {
dev_err ( thermal - > dev ,
" Cannot clear the TJUNC temperature status \n " ) ;
goto err_enable_irq ;
}
/* Now read E_TEMP again: it is acting like a status bit.
* If over - temperature , then this status will be true .
* If not over - temperature , this status will be false .
*/
ret = regmap_read ( thermal - > hw - > regmap ,
DA9062AA_EVENT_B ,
& val ) ;
if ( ret < 0 ) {
dev_err ( thermal - > dev ,
" Cannot check the TJUNC temperature status \n " ) ;
goto err_enable_irq ;
}
if ( val & DA9062AA_E_TEMP_MASK ) {
mutex_lock ( & thermal - > lock ) ;
thermal - > temperature = DA9062_MILLI_CELSIUS ( 125 ) ;
mutex_unlock ( & thermal - > lock ) ;
thermal_zone_device_update ( thermal - > zone ,
THERMAL_EVENT_UNSPECIFIED ) ;
2020-12-17 01:03:37 +03:00
delay = thermal - > zone - > passive_delay_jiffies ;
2018-10-12 10:20:17 +03:00
queue_delayed_work ( system_freezable_wq , & thermal - > work , delay ) ;
2017-03-28 17:43:33 +03:00
return ;
}
mutex_lock ( & thermal - > lock ) ;
thermal - > temperature = DA9062_MILLI_CELSIUS ( 0 ) ;
mutex_unlock ( & thermal - > lock ) ;
thermal_zone_device_update ( thermal - > zone ,
THERMAL_EVENT_UNSPECIFIED ) ;
err_enable_irq :
enable_irq ( thermal - > irq ) ;
}
static irqreturn_t da9062_thermal_irq_handler ( int irq , void * data )
{
struct da9062_thermal * thermal = data ;
disable_irq_nosync ( thermal - > irq ) ;
2018-10-12 10:20:17 +03:00
queue_delayed_work ( system_freezable_wq , & thermal - > work , 0 ) ;
2017-03-28 17:43:33 +03:00
return IRQ_HANDLED ;
}
static int da9062_thermal_get_trip_type ( struct thermal_zone_device * z ,
int trip ,
enum thermal_trip_type * type )
{
struct da9062_thermal * thermal = z - > devdata ;
switch ( trip ) {
case 0 :
* type = THERMAL_TRIP_HOT ;
break ;
default :
dev_err ( thermal - > dev ,
" Driver does not support more than 1 trip-wire \n " ) ;
return - EINVAL ;
}
return 0 ;
}
static int da9062_thermal_get_trip_temp ( struct thermal_zone_device * z ,
int trip ,
int * temp )
{
struct da9062_thermal * thermal = z - > devdata ;
switch ( trip ) {
case 0 :
* temp = DA9062_MILLI_CELSIUS ( 125 ) ;
break ;
default :
dev_err ( thermal - > dev ,
" Driver does not support more than 1 trip-wire \n " ) ;
return - EINVAL ;
}
return 0 ;
}
static int da9062_thermal_get_temp ( struct thermal_zone_device * z ,
int * temp )
{
struct da9062_thermal * thermal = z - > devdata ;
mutex_lock ( & thermal - > lock ) ;
* temp = thermal - > temperature ;
mutex_unlock ( & thermal - > lock ) ;
return 0 ;
}
static struct thermal_zone_device_ops da9062_thermal_ops = {
. get_temp = da9062_thermal_get_temp ,
. get_trip_type = da9062_thermal_get_trip_type ,
. get_trip_temp = da9062_thermal_get_trip_temp ,
} ;
static const struct da9062_thermal_config da9062_config = {
. name = " da9062-thermal " ,
} ;
static const struct of_device_id da9062_compatible_reg_id_table [ ] = {
{ . compatible = " dlg,da9062-thermal " , . data = & da9062_config } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , da9062_compatible_reg_id_table ) ;
static int da9062_thermal_probe ( struct platform_device * pdev )
{
struct da9062 * chip = dev_get_drvdata ( pdev - > dev . parent ) ;
struct da9062_thermal * thermal ;
unsigned int pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD ;
const struct of_device_id * match ;
int ret = 0 ;
match = of_match_node ( da9062_compatible_reg_id_table ,
pdev - > dev . of_node ) ;
if ( ! match )
return - ENXIO ;
if ( pdev - > dev . of_node ) {
if ( ! of_property_read_u32 ( pdev - > dev . of_node ,
" polling-delay-passive " ,
& pp_tmp ) ) {
if ( pp_tmp < DA9062_MIN_POLLING_MS_PERIOD | |
pp_tmp > DA9062_MAX_POLLING_MS_PERIOD ) {
dev_warn ( & pdev - > dev ,
" Out-of-range polling period %d ms \n " ,
pp_tmp ) ;
pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD ;
}
}
}
thermal = devm_kzalloc ( & pdev - > dev , sizeof ( struct da9062_thermal ) ,
GFP_KERNEL ) ;
if ( ! thermal ) {
ret = - ENOMEM ;
goto err ;
}
thermal - > config = match - > data ;
thermal - > hw = chip ;
thermal - > dev = & pdev - > dev ;
INIT_DELAYED_WORK ( & thermal - > work , da9062_thermal_poll_on ) ;
mutex_init ( & thermal - > lock ) ;
thermal - > zone = thermal_zone_device_register ( thermal - > config - > name ,
1 , 0 , thermal ,
& da9062_thermal_ops , NULL , pp_tmp ,
0 ) ;
if ( IS_ERR ( thermal - > zone ) ) {
dev_err ( & pdev - > dev , " Cannot register thermal zone device \n " ) ;
ret = PTR_ERR ( thermal - > zone ) ;
goto err ;
}
thermal: Use mode helpers in drivers
Use thermal_zone_device_{en|dis}able() and thermal_zone_device_is_enabled().
Consequently, all set_mode() implementations in drivers:
- can stop modifying tzd's "mode" member,
- shall stop taking tzd's lock, as it is taken in the helpers
- shall stop calling thermal_zone_device_update() as it is called in the
helpers
- can assume they are called when the mode truly changes, so checks to
verify that can be dropped
Not providing set_mode() by a driver no longer prevents the core from
being able to set tzd's mode, so the relevant check in mode_store() is
removed.
Other comments:
- acpi/thermal.c: tz->thermal_zone->mode will be updated only after we
return from set_mode(), so use function parameter in thermal_set_mode()
instead, no need to call acpi_thermal_check() in set_mode()
- thermal/imx_thermal.c: regmap writes and mode assignment are done in
thermal_zone_device_{en|dis}able() and set_mode() callback
- thermal/intel/intel_quark_dts_thermal.c: soc_dts_{en|dis}able() are a
part of set_mode() callback, so they don't need to modify tzd->mode, and
don't need to fall back to the opposite mode if unsuccessful, as the return
value will be propagated to thermal_zone_device_{en|dis}able() and
ultimately tzd's member will not be changed in thermal_zone_device_set_mode().
- thermal/of-thermal.c: no need to set zone->mode to DISABLED in
of_parse_thermal_zones() as a tzd is kzalloc'ed so mode is DISABLED anyway
Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@collabora.com>
[for acerhdf]
Acked-by: Peter Kaestle <peter@piie.net>
Reviewed-by: Amit Kucheria <amit.kucheria@linaro.org>
Reviewed-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://lore.kernel.org/r/20200629122925.21729-8-andrzej.p@collabora.com
2020-06-29 15:29:21 +03:00
ret = thermal_zone_device_enable ( thermal - > zone ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Cannot enable thermal zone device \n " ) ;
goto err_zone ;
}
2017-03-28 17:43:33 +03:00
dev_dbg ( & pdev - > dev ,
" TJUNC temperature polling period set at %d ms \n " ,
2020-12-17 01:03:37 +03:00
jiffies_to_msecs ( thermal - > zone - > passive_delay_jiffies ) ) ;
2017-03-28 17:43:33 +03:00
ret = platform_get_irq_byname ( pdev , " THERMAL " ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " Failed to get platform IRQ. \n " ) ;
goto err_zone ;
}
thermal - > irq = ret ;
ret = request_threaded_irq ( thermal - > irq , NULL ,
da9062_thermal_irq_handler ,
IRQF_TRIGGER_LOW | IRQF_ONESHOT ,
" THERMAL " , thermal ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" Failed to request thermal device IRQ. \n " ) ;
goto err_zone ;
}
platform_set_drvdata ( pdev , thermal ) ;
return 0 ;
err_zone :
thermal_zone_device_unregister ( thermal - > zone ) ;
err :
return ret ;
}
static int da9062_thermal_remove ( struct platform_device * pdev )
{
struct da9062_thermal * thermal = platform_get_drvdata ( pdev ) ;
free_irq ( thermal - > irq , thermal ) ;
cancel_delayed_work_sync ( & thermal - > work ) ;
thermal_zone_device_unregister ( thermal - > zone ) ;
return 0 ;
}
static struct platform_driver da9062_thermal_driver = {
. probe = da9062_thermal_probe ,
. remove = da9062_thermal_remove ,
. driver = {
. name = " da9062-thermal " ,
. of_match_table = da9062_compatible_reg_id_table ,
} ,
} ;
module_platform_driver ( da9062_thermal_driver ) ;
MODULE_AUTHOR ( " Steve Twiss " ) ;
MODULE_DESCRIPTION ( " Thermal TJUNC device driver for Dialog DA9062 and DA9061 " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:da9062-thermal " ) ;