1 // SPDX-License-Identifier: GPL-2.0
3 * Siemens SIMATIC IPC driver for CMOS battery monitoring
5 * Copyright (c) Siemens AG, 2023
8 * Gerd Haeussler <gerd.haeussler.ext@siemens.com>
9 * Henning Schild <henning.schild@siemens.com>
12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14 #include <linux/delay.h>
16 #include <linux/ioport.h>
17 #include <linux/gpio/machine.h>
18 #include <linux/gpio/consumer.h>
19 #include <linux/hwmon.h>
20 #include <linux/hwmon-sysfs.h>
21 #include <linux/jiffies.h>
22 #include <linux/kernel.h>
23 #include <linux/module.h>
24 #include <linux/platform_device.h>
25 #include <linux/platform_data/x86/simatic-ipc-base.h>
26 #include <linux/sizes.h>
28 #include "simatic-ipc-batt.h"
30 #define BATT_DELAY_MS (1000 * 60 * 60 * 24) /* 24 h delay */
32 #define SIMATIC_IPC_BATT_LEVEL_FULL 3000
33 #define SIMATIC_IPC_BATT_LEVEL_CRIT 2750
34 #define SIMATIC_IPC_BATT_LEVEL_EMPTY 0
36 static struct simatic_ipc_batt {
39 struct gpio_desc *gpios[3];
40 unsigned long last_updated_jiffies;
43 static long simatic_ipc_batt_read_gpio(void)
45 long r = SIMATIC_IPC_BATT_LEVEL_FULL;
48 gpiod_set_value(priv.gpios[2], 1);
52 if (gpiod_get_value_cansleep(priv.gpios[0]))
53 r = SIMATIC_IPC_BATT_LEVEL_EMPTY;
54 else if (gpiod_get_value_cansleep(priv.gpios[1]))
55 r = SIMATIC_IPC_BATT_LEVEL_CRIT;
58 gpiod_set_value(priv.gpios[2], 0);
63 #define SIMATIC_IPC_BATT_PORT_BASE 0x404D
64 static struct resource simatic_ipc_batt_io_res =
65 DEFINE_RES_IO_NAMED(SIMATIC_IPC_BATT_PORT_BASE, SZ_1, KBUILD_MODNAME);
67 static long simatic_ipc_batt_read_io(struct device *dev)
69 long r = SIMATIC_IPC_BATT_LEVEL_FULL;
70 struct resource *res = &simatic_ipc_batt_io_res;
73 if (!request_muxed_region(res->start, resource_size(res), res->name)) {
74 dev_err(dev, "Unable to register IO resource at %pR\n", res);
78 val = inb(SIMATIC_IPC_BATT_PORT_BASE);
79 release_region(simatic_ipc_batt_io_res.start, resource_size(&simatic_ipc_batt_io_res));
82 r = SIMATIC_IPC_BATT_LEVEL_EMPTY;
83 else if (val & (1 << 6))
84 r = SIMATIC_IPC_BATT_LEVEL_CRIT;
89 static long simatic_ipc_batt_read_value(struct device *dev)
91 unsigned long next_update;
93 next_update = priv.last_updated_jiffies + msecs_to_jiffies(BATT_DELAY_MS);
94 if (time_after(jiffies, next_update) || !priv.last_updated_jiffies) {
95 if (priv.devmode == SIMATIC_IPC_DEVICE_227E)
96 priv.current_state = simatic_ipc_batt_read_io(dev);
98 priv.current_state = simatic_ipc_batt_read_gpio();
100 priv.last_updated_jiffies = jiffies;
101 if (priv.current_state < SIMATIC_IPC_BATT_LEVEL_FULL)
102 dev_warn(dev, "CMOS battery needs to be replaced.\n");
105 return priv.current_state;
108 static int simatic_ipc_batt_read(struct device *dev, enum hwmon_sensor_types type,
109 u32 attr, int channel, long *val)
113 *val = simatic_ipc_batt_read_value(dev);
116 *val = SIMATIC_IPC_BATT_LEVEL_CRIT;
125 static umode_t simatic_ipc_batt_is_visible(const void *data, enum hwmon_sensor_types type,
126 u32 attr, int channel)
128 if (attr == hwmon_in_input || attr == hwmon_in_lcrit)
134 static const struct hwmon_ops simatic_ipc_batt_ops = {
135 .is_visible = simatic_ipc_batt_is_visible,
136 .read = simatic_ipc_batt_read,
139 static const struct hwmon_channel_info *simatic_ipc_batt_info[] = {
140 HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LCRIT),
144 static const struct hwmon_chip_info simatic_ipc_batt_chip_info = {
145 .ops = &simatic_ipc_batt_ops,
146 .info = simatic_ipc_batt_info,
149 int simatic_ipc_batt_remove(struct platform_device *pdev, struct gpiod_lookup_table *table)
151 gpiod_remove_lookup_table(table);
154 EXPORT_SYMBOL_GPL(simatic_ipc_batt_remove);
156 int simatic_ipc_batt_probe(struct platform_device *pdev, struct gpiod_lookup_table *table)
158 struct simatic_ipc_platform *plat;
159 struct device *dev = &pdev->dev;
160 struct device *hwmon_dev;
164 plat = pdev->dev.platform_data;
165 priv.devmode = plat->devmode;
167 switch (priv.devmode) {
168 case SIMATIC_IPC_DEVICE_127E:
169 case SIMATIC_IPC_DEVICE_227G:
170 case SIMATIC_IPC_DEVICE_BX_39A:
171 case SIMATIC_IPC_DEVICE_BX_21A:
172 case SIMATIC_IPC_DEVICE_BX_59A:
173 table->dev_id = dev_name(dev);
174 gpiod_add_lookup_table(table);
176 case SIMATIC_IPC_DEVICE_227E:
182 priv.gpios[0] = devm_gpiod_get_index(dev, "CMOSBattery empty", 0, GPIOD_IN);
183 if (IS_ERR(priv.gpios[0])) {
184 err = PTR_ERR(priv.gpios[0]);
185 priv.gpios[0] = NULL;
188 priv.gpios[1] = devm_gpiod_get_index(dev, "CMOSBattery low", 1, GPIOD_IN);
189 if (IS_ERR(priv.gpios[1])) {
190 err = PTR_ERR(priv.gpios[1]);
191 priv.gpios[1] = NULL;
195 if (table->table[2].key) {
196 flags = GPIOD_OUT_HIGH;
197 if (priv.devmode == SIMATIC_IPC_DEVICE_BX_21A ||
198 priv.devmode == SIMATIC_IPC_DEVICE_BX_59A)
199 flags = GPIOD_OUT_LOW;
200 priv.gpios[2] = devm_gpiod_get_index(dev, "CMOSBattery meter", 2, flags);
201 if (IS_ERR(priv.gpios[2])) {
202 err = PTR_ERR(priv.gpios[2]);
203 priv.gpios[2] = NULL;
207 priv.gpios[2] = NULL;
211 hwmon_dev = devm_hwmon_device_register_with_info(dev, KBUILD_MODNAME,
213 &simatic_ipc_batt_chip_info,
215 if (IS_ERR(hwmon_dev)) {
216 err = PTR_ERR(hwmon_dev);
220 /* warn about aging battery even if userspace never reads hwmon */
221 simatic_ipc_batt_read_value(dev);
225 simatic_ipc_batt_remove(pdev, table);
229 EXPORT_SYMBOL_GPL(simatic_ipc_batt_probe);
231 static int simatic_ipc_batt_io_remove(struct platform_device *pdev)
233 return simatic_ipc_batt_remove(pdev, NULL);
236 static int simatic_ipc_batt_io_probe(struct platform_device *pdev)
238 return simatic_ipc_batt_probe(pdev, NULL);
241 static struct platform_driver simatic_ipc_batt_driver = {
242 .probe = simatic_ipc_batt_io_probe,
243 .remove = simatic_ipc_batt_io_remove,
245 .name = KBUILD_MODNAME,
249 module_platform_driver(simatic_ipc_batt_driver);
251 MODULE_LICENSE("GPL");
252 MODULE_ALIAS("platform:" KBUILD_MODNAME);
253 MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");