Skip to content

Commit 7e581c1

Browse files
Gerhard Engledergroeck
authored andcommitted
hwmon: Add KEBA battery monitoring controller support
The KEBA battery monitoring controller is found in the system FPGA of KEBA PLC devices. It puts a load on the coin cell battery to check the state of the battery. If the coin cell battery is nearly empty, then the user space is signaled with a hwmon alarm. The auxiliary device for this driver is instantiated by the cp500 misc driver. Signed-off-by: Gerhard Engleder <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Guenter Roeck <[email protected]>
1 parent 0d01110 commit 7e581c1

File tree

5 files changed

+219
-0
lines changed

5 files changed

+219
-0
lines changed

Documentation/hwmon/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Hardware Monitoring Kernel Drivers
106106
jc42
107107
k10temp
108108
k8temp
109+
kbatt
109110
lan966x
110111
lineage-pem
111112
lm25066

Documentation/hwmon/kbatt.rst

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.. SPDX-License-Identifier: GPL-2.0
2+
3+
Kernel driver kbatt
4+
===================
5+
6+
Supported chips:
7+
8+
* KEBA battery monitoring controller (IP core in FPGA)
9+
10+
Prefix: 'kbatt'
11+
12+
Authors:
13+
14+
Gerhard Engleder <[email protected]>
15+
Petar Bojanic <[email protected]>
16+
17+
Description
18+
-----------
19+
20+
The KEBA battery monitoring controller is an IP core for FPGAs, which
21+
monitors the health of a coin cell battery. The coin cell battery is
22+
typically used to supply the RTC during power off to keep the current
23+
time. E.g., the CP500 FPGA includes this IP core to monitor the coin cell
24+
battery of PLCs and the corresponding cp500 driver creates an auxiliary
25+
device for the kbatt driver.
26+
27+
This driver provides information about the coin cell battery health to
28+
user space. Actually the user space shall be informed that the coin cell
29+
battery is nearly empty and needs to be replaced.
30+
31+
The coin cell battery must be tested actively to get to know if its nearly
32+
empty or not. Therefore, a load is put on the coin cell battery and the
33+
resulting voltage is evaluated. This evaluation is done by some hard wired
34+
analog logic, which compares the voltage to a defined limit. If the
35+
voltage is above the limit, then the coin cell battery is assumed to be
36+
ok. If the voltage is below the limit, then the coin cell battery is
37+
nearly empty (or broken, removed, ...) and shall be replaced by a new one.
38+
The KEBA battery monitoring controller allows to start the test of the
39+
coin cell battery and to get the result if the voltage is above or below
40+
the limit. The actual voltage is not available. Only the information if
41+
the voltage is below a limit is available.
42+
43+
The test load, which is put on the coin cell battery for the health check,
44+
is similar to the load during power off. Therefore, the lifetime of the
45+
coin cell battery is reduced directly by the duration of each test. To
46+
limit the negative impact to the lifetime the test is limited to at most
47+
once every 10 seconds. The test load is put on the coin cell battery for
48+
100ms. Thus, in worst case the coin cell battery lifetime is reduced by
49+
1% of the uptime or 3.65 days per year. As the coin cell battery lasts
50+
multiple years, this lifetime reduction negligible.
51+
52+
This driver only provides a single alarm attribute, which is raised when
53+
the coin cell battery is nearly empty.
54+
55+
====================== ==== ===================================================
56+
Attribute R/W Contents
57+
====================== ==== ===================================================
58+
in0_min_alarm R voltage of coin cell battery under load is below
59+
limit
60+
====================== ==== ===================================================

drivers/hwmon/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,16 @@ config SENSORS_K10TEMP
335335
This driver can also be built as a module. If so, the module
336336
will be called k10temp.
337337

338+
config SENSORS_KBATT
339+
tristate "KEBA battery controller support"
340+
depends on KEBA_CP500
341+
help
342+
This driver supports the battery monitoring controller found in
343+
KEBA system FPGA devices.
344+
345+
This driver can also be built as a module. If so, the module
346+
will be called kbatt.
347+
338348
config SENSORS_FAM15H_POWER
339349
tristate "AMD Family 15h processor power"
340350
depends on X86 && PCI && CPU_SUP_AMD

drivers/hwmon/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o
110110
obj-$(CONFIG_SENSORS_JC42) += jc42.o
111111
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
112112
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
113+
obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
113114
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
114115
obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
115116
obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o

drivers/hwmon/kbatt.c

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (C) 2025 KEBA Industrial Automation GmbH
4+
*
5+
* Driver for KEBA battery monitoring controller FPGA IP core
6+
*/
7+
8+
#include <linux/hwmon.h>
9+
#include <linux/io.h>
10+
#include <linux/delay.h>
11+
#include <linux/module.h>
12+
#include <linux/device.h>
13+
#include <linux/auxiliary_bus.h>
14+
#include <linux/misc/keba.h>
15+
#include <linux/mutex.h>
16+
17+
#define KBATT "kbatt"
18+
19+
#define KBATT_CONTROL_REG 0x4
20+
#define KBATT_CONTROL_BAT_TEST 0x01
21+
22+
#define KBATT_STATUS_REG 0x8
23+
#define KBATT_STATUS_BAT_OK 0x01
24+
25+
#define KBATT_MAX_UPD_INTERVAL (10 * HZ)
26+
#define KBATT_SETTLE_TIME_US (100 * USEC_PER_MSEC)
27+
28+
struct kbatt {
29+
/* update lock */
30+
struct mutex lock;
31+
void __iomem *base;
32+
33+
unsigned long next_update; /* in jiffies */
34+
bool alarm;
35+
};
36+
37+
static bool kbatt_alarm(struct kbatt *kbatt)
38+
{
39+
mutex_lock(&kbatt->lock);
40+
41+
if (!kbatt->next_update || time_after(jiffies, kbatt->next_update)) {
42+
/* switch load on */
43+
iowrite8(KBATT_CONTROL_BAT_TEST,
44+
kbatt->base + KBATT_CONTROL_REG);
45+
46+
/* wait some time to let things settle */
47+
fsleep(KBATT_SETTLE_TIME_US);
48+
49+
/* check battery state */
50+
if (ioread8(kbatt->base + KBATT_STATUS_REG) &
51+
KBATT_STATUS_BAT_OK)
52+
kbatt->alarm = false;
53+
else
54+
kbatt->alarm = true;
55+
56+
/* switch load off */
57+
iowrite8(0, kbatt->base + KBATT_CONTROL_REG);
58+
59+
kbatt->next_update = jiffies + KBATT_MAX_UPD_INTERVAL;
60+
}
61+
62+
mutex_unlock(&kbatt->lock);
63+
64+
return kbatt->alarm;
65+
}
66+
67+
static int kbatt_read(struct device *dev, enum hwmon_sensor_types type,
68+
u32 attr, int channel, long *val)
69+
{
70+
struct kbatt *kbatt = dev_get_drvdata(dev);
71+
72+
*val = kbatt_alarm(kbatt) ? 1 : 0;
73+
74+
return 0;
75+
}
76+
77+
static umode_t kbatt_is_visible(const void *data, enum hwmon_sensor_types type,
78+
u32 attr, int channel)
79+
{
80+
if (channel == 0 && attr == hwmon_in_min_alarm)
81+
return 0444;
82+
83+
return 0;
84+
}
85+
86+
static const struct hwmon_channel_info *kbatt_info[] = {
87+
HWMON_CHANNEL_INFO(in,
88+
/* 0: input minimum alarm channel */
89+
HWMON_I_MIN_ALARM),
90+
NULL
91+
};
92+
93+
static const struct hwmon_ops kbatt_hwmon_ops = {
94+
.is_visible = kbatt_is_visible,
95+
.read = kbatt_read,
96+
};
97+
98+
static const struct hwmon_chip_info kbatt_chip_info = {
99+
.ops = &kbatt_hwmon_ops,
100+
.info = kbatt_info,
101+
};
102+
103+
static int kbatt_probe(struct auxiliary_device *auxdev,
104+
const struct auxiliary_device_id *id)
105+
{
106+
struct keba_batt_auxdev *kbatt_auxdev =
107+
container_of(auxdev, struct keba_batt_auxdev, auxdev);
108+
struct device *dev = &auxdev->dev;
109+
struct device *hwmon_dev;
110+
struct kbatt *kbatt;
111+
int retval;
112+
113+
kbatt = devm_kzalloc(dev, sizeof(*kbatt), GFP_KERNEL);
114+
if (!kbatt)
115+
return -ENOMEM;
116+
117+
retval = devm_mutex_init(dev, &kbatt->lock);
118+
if (retval)
119+
return retval;
120+
121+
kbatt->base = devm_ioremap_resource(dev, &kbatt_auxdev->io);
122+
if (IS_ERR(kbatt->base))
123+
return PTR_ERR(kbatt->base);
124+
125+
hwmon_dev = devm_hwmon_device_register_with_info(dev, KBATT, kbatt,
126+
&kbatt_chip_info,
127+
NULL);
128+
return PTR_ERR_OR_ZERO(hwmon_dev);
129+
}
130+
131+
static const struct auxiliary_device_id kbatt_devtype_aux[] = {
132+
{ .name = "keba.batt" },
133+
{}
134+
};
135+
MODULE_DEVICE_TABLE(auxiliary, kbatt_devtype_aux);
136+
137+
static struct auxiliary_driver kbatt_driver_aux = {
138+
.name = KBATT,
139+
.id_table = kbatt_devtype_aux,
140+
.probe = kbatt_probe,
141+
};
142+
module_auxiliary_driver(kbatt_driver_aux);
143+
144+
MODULE_AUTHOR("Petar Bojanic <[email protected]>");
145+
MODULE_AUTHOR("Gerhard Engleder <[email protected]>");
146+
MODULE_DESCRIPTION("KEBA battery monitoring controller driver");
147+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)