2f46e41738
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Link: https://lore.kernel.org/r/20230927081040.2198742-25-u.kleine-koenig@pengutronix.de Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
253 lines
6.2 KiB
C
253 lines
6.2 KiB
C
// 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) {
|
|
if (priv.devmode == SIMATIC_IPC_DEVICE_227E)
|
|
priv.current_state = simatic_ipc_batt_read_io(dev);
|
|
else
|
|
priv.current_state = simatic_ipc_batt_read_gpio();
|
|
|
|
priv.last_updated_jiffies = jiffies;
|
|
if (priv.current_state < SIMATIC_IPC_BATT_LEVEL_FULL)
|
|
dev_warn(dev, "CMOS battery needs to be replaced.\n");
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
void simatic_ipc_batt_remove(struct platform_device *pdev, struct gpiod_lookup_table *table)
|
|
{
|
|
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;
|
|
unsigned long flags;
|
|
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:
|
|
case SIMATIC_IPC_DEVICE_BX_59A:
|
|
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) {
|
|
flags = GPIOD_OUT_HIGH;
|
|
if (priv.devmode == SIMATIC_IPC_DEVICE_BX_21A ||
|
|
priv.devmode == SIMATIC_IPC_DEVICE_BX_59A)
|
|
flags = GPIOD_OUT_LOW;
|
|
priv.gpios[2] = devm_gpiod_get_index(dev, "CMOSBattery meter", 2, flags);
|
|
if (IS_ERR(priv.gpios[2])) {
|
|
err = PTR_ERR(priv.gpios[2]);
|
|
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);
|
|
|
|
static void simatic_ipc_batt_io_remove(struct platform_device *pdev)
|
|
{
|
|
simatic_ipc_batt_remove(pdev, NULL);
|
|
}
|
|
|
|
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,
|
|
.remove_new = simatic_ipc_batt_io_remove,
|
|
.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>");
|