]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
hwmon: Add KEBA battery monitoring controller support
authorGerhard Engleder <eg@keba.com>
Wed, 9 Apr 2025 19:08:30 +0000 (21:08 +0200)
committerGuenter Roeck <linux@roeck-us.net>
Wed, 23 Apr 2025 14:18:27 +0000 (07:18 -0700)
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 <eg@keba.com>
Link: https://lore.kernel.org/r/20250409190830.60489-1-gerhard@engleder-embedded.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/index.rst
Documentation/hwmon/kbatt.rst [new file with mode: 0644]
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/kbatt.c [new file with mode: 0644]

index f0ddf6222c44d454bde2690ec8306b175f6fbd3a..da5895115724df35416b88fcd6edb13c2d5b07f2 100644 (file)
@@ -106,6 +106,7 @@ Hardware Monitoring Kernel Drivers
    jc42
    k10temp
    k8temp
+   kbatt
    lan966x
    lineage-pem
    lm25066
diff --git a/Documentation/hwmon/kbatt.rst b/Documentation/hwmon/kbatt.rst
new file mode 100644 (file)
index 0000000..b72718c
--- /dev/null
@@ -0,0 +1,60 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver kbatt
+===================
+
+Supported chips:
+
+  * KEBA battery monitoring controller (IP core in FPGA)
+
+    Prefix: 'kbatt'
+
+Authors:
+
+       Gerhard Engleder <eg@keba.com>
+       Petar Bojanic <boja@keba.com>
+
+Description
+-----------
+
+The KEBA battery monitoring controller is an IP core for FPGAs, which
+monitors the health of a coin cell battery. The coin cell battery is
+typically used to supply the RTC during power off to keep the current
+time. E.g., the CP500 FPGA includes this IP core to monitor the coin cell
+battery of PLCs and the corresponding cp500 driver creates an auxiliary
+device for the kbatt driver.
+
+This driver provides information about the coin cell battery health to
+user space. Actually the user space shall be informed that the coin cell
+battery is nearly empty and needs to be replaced.
+
+The coin cell battery must be tested actively to get to know if its nearly
+empty or not. Therefore, a load is put on the coin cell battery and the
+resulting voltage is evaluated. This evaluation is done by some hard wired
+analog logic, which compares the voltage to a defined limit. If the
+voltage is above the limit, then the coin cell battery is assumed to be
+ok. If the voltage is below the limit, then the coin cell battery is
+nearly empty (or broken, removed, ...) and shall be replaced by a new one.
+The KEBA battery monitoring controller allows to start the test of the
+coin cell battery and to get the result if the voltage is above or below
+the limit. The actual voltage is not available. Only the information if
+the voltage is below a limit is available.
+
+The test load, which is put on the coin cell battery for the health check,
+is similar to the load during power off. Therefore, the lifetime of the
+coin cell battery is reduced directly by the duration of each test. To
+limit the negative impact to the lifetime the test is limited to at most
+once every 10 seconds. The test load is put on the coin cell battery for
+100ms. Thus, in worst case the coin cell battery lifetime is reduced by
+1% of the uptime or 3.65 days per year. As the coin cell battery lasts
+multiple years, this lifetime reduction negligible.
+
+This driver only provides a single alarm attribute, which is raised when
+the coin cell battery is nearly empty.
+
+====================== ==== ===================================================
+Attribute              R/W  Contents
+====================== ==== ===================================================
+in0_min_alarm          R    voltage of coin cell battery under load is below
+                            limit
+====================== ==== ===================================================
index f91f713b0105d7bdca51b362ab9cbad523bb568c..832d7e5f9f7b6c83c7d2a511698bd4df39651e80 100644 (file)
@@ -335,6 +335,16 @@ config SENSORS_K10TEMP
          This driver can also be built as a module. If so, the module
          will be called k10temp.
 
+config SENSORS_KBATT
+       tristate "KEBA battery controller support"
+       depends on KEBA_CP500
+       help
+         This driver supports the battery monitoring controller found in
+         KEBA system FPGA devices.
+
+         This driver can also be built as a module. If so, the module
+         will be called kbatt.
+
 config SENSORS_FAM15H_POWER
        tristate "AMD Family 15h processor power"
        depends on X86 && PCI && CPU_SUP_AMD
index 766c652ef22bebf40bd4fba45b417f3c473ed5ef..af18deb0422e5b7d89b8a1a4b2ccdf99e8674d7d 100644 (file)
@@ -110,6 +110,7 @@ obj-$(CONFIG_SENSORS_IT87)  += it87.o
 obj-$(CONFIG_SENSORS_JC42)     += jc42.o
 obj-$(CONFIG_SENSORS_K8TEMP)   += k8temp.o
 obj-$(CONFIG_SENSORS_K10TEMP)  += k10temp.o
+obj-$(CONFIG_SENSORS_KBATT)    += kbatt.o
 obj-$(CONFIG_SENSORS_LAN966X)  += lan966x-hwmon.o
 obj-$(CONFIG_SENSORS_LENOVO_EC)        += lenovo-ec-sensors.o
 obj-$(CONFIG_SENSORS_LINEAGE)  += lineage-pem.o
diff --git a/drivers/hwmon/kbatt.c b/drivers/hwmon/kbatt.c
new file mode 100644 (file)
index 0000000..501b8f4
--- /dev/null
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 KEBA Industrial Automation GmbH
+ *
+ * Driver for KEBA battery monitoring controller FPGA IP core
+ */
+
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/misc/keba.h>
+#include <linux/mutex.h>
+
+#define KBATT "kbatt"
+
+#define KBATT_CONTROL_REG              0x4
+#define   KBATT_CONTROL_BAT_TEST       0x01
+
+#define KBATT_STATUS_REG               0x8
+#define   KBATT_STATUS_BAT_OK          0x01
+
+#define KBATT_MAX_UPD_INTERVAL (10 * HZ)
+#define KBATT_SETTLE_TIME_US   (100 * USEC_PER_MSEC)
+
+struct kbatt {
+       /* update lock */
+       struct mutex lock;
+       void __iomem *base;
+
+       unsigned long next_update; /* in jiffies */
+       bool alarm;
+};
+
+static bool kbatt_alarm(struct kbatt *kbatt)
+{
+       mutex_lock(&kbatt->lock);
+
+       if (!kbatt->next_update || time_after(jiffies, kbatt->next_update)) {
+               /* switch load on */
+               iowrite8(KBATT_CONTROL_BAT_TEST,
+                        kbatt->base + KBATT_CONTROL_REG);
+
+               /* wait some time to let things settle */
+               fsleep(KBATT_SETTLE_TIME_US);
+
+               /* check battery state */
+               if (ioread8(kbatt->base + KBATT_STATUS_REG) &
+                   KBATT_STATUS_BAT_OK)
+                       kbatt->alarm = false;
+               else
+                       kbatt->alarm = true;
+
+               /* switch load off */
+               iowrite8(0, kbatt->base + KBATT_CONTROL_REG);
+
+               kbatt->next_update = jiffies + KBATT_MAX_UPD_INTERVAL;
+       }
+
+       mutex_unlock(&kbatt->lock);
+
+       return kbatt->alarm;
+}
+
+static int kbatt_read(struct device *dev, enum hwmon_sensor_types type,
+                     u32 attr, int channel, long *val)
+{
+       struct kbatt *kbatt = dev_get_drvdata(dev);
+
+       *val = kbatt_alarm(kbatt) ? 1 : 0;
+
+       return 0;
+}
+
+static umode_t kbatt_is_visible(const void *data, enum hwmon_sensor_types type,
+                               u32 attr, int channel)
+{
+       if (channel == 0 && attr == hwmon_in_min_alarm)
+               return 0444;
+
+       return 0;
+}
+
+static const struct hwmon_channel_info *kbatt_info[] = {
+       HWMON_CHANNEL_INFO(in,
+                          /* 0: input minimum alarm channel */
+                          HWMON_I_MIN_ALARM),
+       NULL
+};
+
+static const struct hwmon_ops kbatt_hwmon_ops = {
+       .is_visible = kbatt_is_visible,
+       .read = kbatt_read,
+};
+
+static const struct hwmon_chip_info kbatt_chip_info = {
+       .ops = &kbatt_hwmon_ops,
+       .info = kbatt_info,
+};
+
+static int kbatt_probe(struct auxiliary_device *auxdev,
+                      const struct auxiliary_device_id *id)
+{
+       struct keba_batt_auxdev *kbatt_auxdev =
+               container_of(auxdev, struct keba_batt_auxdev, auxdev);
+       struct device *dev = &auxdev->dev;
+       struct device *hwmon_dev;
+       struct kbatt *kbatt;
+       int retval;
+
+       kbatt = devm_kzalloc(dev, sizeof(*kbatt), GFP_KERNEL);
+       if (!kbatt)
+               return -ENOMEM;
+
+       retval = devm_mutex_init(dev, &kbatt->lock);
+       if (retval)
+               return retval;
+
+       kbatt->base = devm_ioremap_resource(dev, &kbatt_auxdev->io);
+       if (IS_ERR(kbatt->base))
+               return PTR_ERR(kbatt->base);
+
+       hwmon_dev = devm_hwmon_device_register_with_info(dev, KBATT, kbatt,
+                                                        &kbatt_chip_info,
+                                                        NULL);
+       return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct auxiliary_device_id kbatt_devtype_aux[] = {
+       { .name = "keba.batt" },
+       {}
+};
+MODULE_DEVICE_TABLE(auxiliary, kbatt_devtype_aux);
+
+static struct auxiliary_driver kbatt_driver_aux = {
+       .name = KBATT,
+       .id_table = kbatt_devtype_aux,
+       .probe = kbatt_probe,
+};
+module_auxiliary_driver(kbatt_driver_aux);
+
+MODULE_AUTHOR("Petar Bojanic <boja@keba.com>");
+MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
+MODULE_DESCRIPTION("KEBA battery monitoring controller driver");
+MODULE_LICENSE("GPL");