2023-07-06 17:48:31 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for CMOS battery monitoring
*
* Copyright ( c ) Siemens AG , 2023
*
* Authors :
* Gerd Haeussler < gerd . haeussler . ext @ siemens . com >
* Henning Schild < henning . schild @ siemens . com >
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/ioport.h>
# include <linux/gpio/machine.h>
# include <linux/gpio/consumer.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/jiffies.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/platform_data/x86/simatic-ipc-base.h>
# include <linux/sizes.h>
# include "simatic-ipc-batt.h"
# define BATT_DELAY_MS (1000 * 60 * 60 * 24) /* 24 h delay */
# define SIMATIC_IPC_BATT_LEVEL_FULL 3000
# define SIMATIC_IPC_BATT_LEVEL_CRIT 2750
# define SIMATIC_IPC_BATT_LEVEL_EMPTY 0
static struct simatic_ipc_batt {
u8 devmode ;
long current_state ;
struct gpio_desc * gpios [ 3 ] ;
unsigned long last_updated_jiffies ;
} priv ;
static long simatic_ipc_batt_read_gpio ( void )
{
long r = SIMATIC_IPC_BATT_LEVEL_FULL ;
if ( priv . gpios [ 2 ] ) {
gpiod_set_value ( priv . gpios [ 2 ] , 1 ) ;
msleep ( 150 ) ;
}
if ( gpiod_get_value_cansleep ( priv . gpios [ 0 ] ) )
r = SIMATIC_IPC_BATT_LEVEL_EMPTY ;
else if ( gpiod_get_value_cansleep ( priv . gpios [ 1 ] ) )
r = SIMATIC_IPC_BATT_LEVEL_CRIT ;
if ( priv . gpios [ 2 ] )
gpiod_set_value ( priv . gpios [ 2 ] , 0 ) ;
return r ;
}
# define SIMATIC_IPC_BATT_PORT_BASE 0x404D
static struct resource simatic_ipc_batt_io_res =
DEFINE_RES_IO_NAMED ( SIMATIC_IPC_BATT_PORT_BASE , SZ_1 , KBUILD_MODNAME ) ;
static long simatic_ipc_batt_read_io ( struct device * dev )
{
long r = SIMATIC_IPC_BATT_LEVEL_FULL ;
struct resource * res = & simatic_ipc_batt_io_res ;
u8 val ;
if ( ! request_muxed_region ( res - > start , resource_size ( res ) , res - > name ) ) {
dev_err ( dev , " Unable to register IO resource at %pR \n " , res ) ;
return - EBUSY ;
}
val = inb ( SIMATIC_IPC_BATT_PORT_BASE ) ;
release_region ( simatic_ipc_batt_io_res . start , resource_size ( & simatic_ipc_batt_io_res ) ) ;
if ( val & ( 1 < < 7 ) )
r = SIMATIC_IPC_BATT_LEVEL_EMPTY ;
else if ( val & ( 1 < < 6 ) )
r = SIMATIC_IPC_BATT_LEVEL_CRIT ;
return r ;
}
static long simatic_ipc_batt_read_value ( struct device * dev )
{
unsigned long next_update ;
next_update = priv . last_updated_jiffies + msecs_to_jiffies ( BATT_DELAY_MS ) ;
if ( time_after ( jiffies , next_update ) | | ! priv . last_updated_jiffies ) {
2023-07-28 10:36:51 +02:00
if ( priv . devmode = = SIMATIC_IPC_DEVICE_227E )
2023-07-06 17:48:31 +02:00
priv . current_state = simatic_ipc_batt_read_io ( dev ) ;
2023-07-28 10:36:51 +02:00
else
priv . current_state = simatic_ipc_batt_read_gpio ( ) ;
2023-07-06 17:48:31 +02:00
priv . last_updated_jiffies = jiffies ;
if ( priv . current_state < SIMATIC_IPC_BATT_LEVEL_FULL )
2023-07-28 10:36:51 +02:00
dev_warn ( dev , " CMOS battery needs to be replaced. \n " ) ;
2023-07-06 17:48:31 +02:00
}
return priv . current_state ;
}
static int simatic_ipc_batt_read ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel , long * val )
{
switch ( attr ) {
case hwmon_in_input :
* val = simatic_ipc_batt_read_value ( dev ) ;
break ;
case hwmon_in_lcrit :
* val = SIMATIC_IPC_BATT_LEVEL_CRIT ;
break ;
default :
return - EOPNOTSUPP ;
}
return 0 ;
}
static umode_t simatic_ipc_batt_is_visible ( const void * data , enum hwmon_sensor_types type ,
u32 attr , int channel )
{
if ( attr = = hwmon_in_input | | attr = = hwmon_in_lcrit )
return 0444 ;
return 0 ;
}
static const struct hwmon_ops simatic_ipc_batt_ops = {
. is_visible = simatic_ipc_batt_is_visible ,
. read = simatic_ipc_batt_read ,
} ;
static const struct hwmon_channel_info * simatic_ipc_batt_info [ ] = {
HWMON_CHANNEL_INFO ( in , HWMON_I_INPUT | HWMON_I_LCRIT ) ,
NULL
} ;
static const struct hwmon_chip_info simatic_ipc_batt_chip_info = {
. ops = & simatic_ipc_batt_ops ,
. info = simatic_ipc_batt_info ,
} ;
2023-09-27 10:10:36 +02:00
void simatic_ipc_batt_remove ( struct platform_device * pdev , struct gpiod_lookup_table * table )
2023-07-06 17:48:31 +02:00
{
gpiod_remove_lookup_table ( table ) ;
}
EXPORT_SYMBOL_GPL ( simatic_ipc_batt_remove ) ;
int simatic_ipc_batt_probe ( struct platform_device * pdev , struct gpiod_lookup_table * table )
{
struct simatic_ipc_platform * plat ;
struct device * dev = & pdev - > dev ;
struct device * hwmon_dev ;
2023-07-28 10:36:51 +02:00
unsigned long flags ;
2023-07-06 17:48:31 +02:00
int err ;
plat = pdev - > dev . platform_data ;
priv . devmode = plat - > devmode ;
switch ( priv . devmode ) {
case SIMATIC_IPC_DEVICE_127E :
case SIMATIC_IPC_DEVICE_227G :
case SIMATIC_IPC_DEVICE_BX_39A :
case SIMATIC_IPC_DEVICE_BX_21A :
2023-07-31 15:21:48 +08:00
case SIMATIC_IPC_DEVICE_BX_59A :
2023-07-06 17:48:31 +02:00
table - > dev_id = dev_name ( dev ) ;
gpiod_add_lookup_table ( table ) ;
break ;
case SIMATIC_IPC_DEVICE_227E :
goto nogpio ;
default :
return - ENODEV ;
}
priv . gpios [ 0 ] = devm_gpiod_get_index ( dev , " CMOSBattery empty " , 0 , GPIOD_IN ) ;
if ( IS_ERR ( priv . gpios [ 0 ] ) ) {
err = PTR_ERR ( priv . gpios [ 0 ] ) ;
priv . gpios [ 0 ] = NULL ;
goto out ;
}
priv . gpios [ 1 ] = devm_gpiod_get_index ( dev , " CMOSBattery low " , 1 , GPIOD_IN ) ;
if ( IS_ERR ( priv . gpios [ 1 ] ) ) {
err = PTR_ERR ( priv . gpios [ 1 ] ) ;
priv . gpios [ 1 ] = NULL ;
goto out ;
}
if ( table - > table [ 2 ] . key ) {
2023-07-28 10:36:51 +02:00
flags = GPIOD_OUT_HIGH ;
2023-08-03 01:35:15 +08:00
if ( priv . devmode = = SIMATIC_IPC_DEVICE_BX_21A | |
priv . devmode = = SIMATIC_IPC_DEVICE_BX_59A )
2023-07-28 10:36:51 +02:00
flags = GPIOD_OUT_LOW ;
priv . gpios [ 2 ] = devm_gpiod_get_index ( dev , " CMOSBattery meter " , 2 , flags ) ;
2023-07-06 17:48:31 +02:00
if ( IS_ERR ( priv . gpios [ 2 ] ) ) {
2023-08-09 16:12:27 +08:00
err = PTR_ERR ( priv . gpios [ 2 ] ) ;
2023-07-06 17:48:31 +02:00
priv . gpios [ 2 ] = NULL ;
goto out ;
}
} else {
priv . gpios [ 2 ] = NULL ;
}
nogpio :
hwmon_dev = devm_hwmon_device_register_with_info ( dev , KBUILD_MODNAME ,
& priv ,
& simatic_ipc_batt_chip_info ,
NULL ) ;
if ( IS_ERR ( hwmon_dev ) ) {
err = PTR_ERR ( hwmon_dev ) ;
goto out ;
}
/* warn about aging battery even if userspace never reads hwmon */
simatic_ipc_batt_read_value ( dev ) ;
return 0 ;
out :
simatic_ipc_batt_remove ( pdev , table ) ;
return err ;
}
EXPORT_SYMBOL_GPL ( simatic_ipc_batt_probe ) ;
2023-09-27 10:10:37 +02:00
static void simatic_ipc_batt_io_remove ( struct platform_device * pdev )
2023-07-06 17:48:31 +02:00
{
2023-09-27 10:10:36 +02:00
simatic_ipc_batt_remove ( pdev , NULL ) ;
2023-07-06 17:48:31 +02:00
}
static int simatic_ipc_batt_io_probe ( struct platform_device * pdev )
{
return simatic_ipc_batt_probe ( pdev , NULL ) ;
}
static struct platform_driver simatic_ipc_batt_driver = {
. probe = simatic_ipc_batt_io_probe ,
2023-09-27 10:10:37 +02:00
. remove_new = simatic_ipc_batt_io_remove ,
2023-07-06 17:48:31 +02:00
. driver = {
. name = KBUILD_MODNAME ,
} ,
} ;
module_platform_driver ( simatic_ipc_batt_driver ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform: " KBUILD_MODNAME ) ;
MODULE_AUTHOR ( " Henning Schild <henning.schild@siemens.com> " ) ;