19bc289b8SHenning Schild // SPDX-License-Identifier: GPL-2.0
29bc289b8SHenning Schild /*
39bc289b8SHenning Schild  * Siemens SIMATIC IPC driver for CMOS battery monitoring
49bc289b8SHenning Schild  *
59bc289b8SHenning Schild  * Copyright (c) Siemens AG, 2023
69bc289b8SHenning Schild  *
79bc289b8SHenning Schild  * Authors:
89bc289b8SHenning Schild  *  Gerd Haeussler <gerd.haeussler.ext@siemens.com>
99bc289b8SHenning Schild  *  Henning Schild <henning.schild@siemens.com>
109bc289b8SHenning Schild  */
119bc289b8SHenning Schild 
129bc289b8SHenning Schild #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
139bc289b8SHenning Schild 
149bc289b8SHenning Schild #include <linux/delay.h>
159bc289b8SHenning Schild #include <linux/io.h>
169bc289b8SHenning Schild #include <linux/ioport.h>
179bc289b8SHenning Schild #include <linux/gpio/machine.h>
189bc289b8SHenning Schild #include <linux/gpio/consumer.h>
199bc289b8SHenning Schild #include <linux/hwmon.h>
209bc289b8SHenning Schild #include <linux/hwmon-sysfs.h>
219bc289b8SHenning Schild #include <linux/jiffies.h>
229bc289b8SHenning Schild #include <linux/kernel.h>
239bc289b8SHenning Schild #include <linux/module.h>
249bc289b8SHenning Schild #include <linux/platform_device.h>
259bc289b8SHenning Schild #include <linux/platform_data/x86/simatic-ipc-base.h>
269bc289b8SHenning Schild #include <linux/sizes.h>
279bc289b8SHenning Schild 
289bc289b8SHenning Schild #include "simatic-ipc-batt.h"
299bc289b8SHenning Schild 
309bc289b8SHenning Schild #define BATT_DELAY_MS	(1000 * 60 * 60 * 24)	/* 24 h delay */
319bc289b8SHenning Schild 
329bc289b8SHenning Schild #define SIMATIC_IPC_BATT_LEVEL_FULL	3000
339bc289b8SHenning Schild #define SIMATIC_IPC_BATT_LEVEL_CRIT	2750
349bc289b8SHenning Schild #define SIMATIC_IPC_BATT_LEVEL_EMPTY	   0
359bc289b8SHenning Schild 
369bc289b8SHenning Schild static struct simatic_ipc_batt {
379bc289b8SHenning Schild 	u8 devmode;
389bc289b8SHenning Schild 	long current_state;
399bc289b8SHenning Schild 	struct gpio_desc *gpios[3];
409bc289b8SHenning Schild 	unsigned long last_updated_jiffies;
419bc289b8SHenning Schild } priv;
429bc289b8SHenning Schild 
simatic_ipc_batt_read_gpio(void)439bc289b8SHenning Schild static long simatic_ipc_batt_read_gpio(void)
449bc289b8SHenning Schild {
459bc289b8SHenning Schild 	long r = SIMATIC_IPC_BATT_LEVEL_FULL;
469bc289b8SHenning Schild 
479bc289b8SHenning Schild 	if (priv.gpios[2]) {
489bc289b8SHenning Schild 		gpiod_set_value(priv.gpios[2], 1);
499bc289b8SHenning Schild 		msleep(150);
509bc289b8SHenning Schild 	}
519bc289b8SHenning Schild 
529bc289b8SHenning Schild 	if (gpiod_get_value_cansleep(priv.gpios[0]))
539bc289b8SHenning Schild 		r = SIMATIC_IPC_BATT_LEVEL_EMPTY;
549bc289b8SHenning Schild 	else if (gpiod_get_value_cansleep(priv.gpios[1]))
559bc289b8SHenning Schild 		r = SIMATIC_IPC_BATT_LEVEL_CRIT;
569bc289b8SHenning Schild 
579bc289b8SHenning Schild 	if (priv.gpios[2])
589bc289b8SHenning Schild 		gpiod_set_value(priv.gpios[2], 0);
599bc289b8SHenning Schild 
609bc289b8SHenning Schild 	return r;
619bc289b8SHenning Schild }
629bc289b8SHenning Schild 
639bc289b8SHenning Schild #define SIMATIC_IPC_BATT_PORT_BASE	0x404D
649bc289b8SHenning Schild static struct resource simatic_ipc_batt_io_res =
659bc289b8SHenning Schild 	DEFINE_RES_IO_NAMED(SIMATIC_IPC_BATT_PORT_BASE, SZ_1, KBUILD_MODNAME);
669bc289b8SHenning Schild 
simatic_ipc_batt_read_io(struct device * dev)679bc289b8SHenning Schild static long simatic_ipc_batt_read_io(struct device *dev)
689bc289b8SHenning Schild {
699bc289b8SHenning Schild 	long r = SIMATIC_IPC_BATT_LEVEL_FULL;
709bc289b8SHenning Schild 	struct resource *res = &simatic_ipc_batt_io_res;
719bc289b8SHenning Schild 	u8 val;
729bc289b8SHenning Schild 
739bc289b8SHenning Schild 	if (!request_muxed_region(res->start, resource_size(res), res->name)) {
749bc289b8SHenning Schild 		dev_err(dev, "Unable to register IO resource at %pR\n", res);
759bc289b8SHenning Schild 		return -EBUSY;
769bc289b8SHenning Schild 	}
779bc289b8SHenning Schild 
789bc289b8SHenning Schild 	val = inb(SIMATIC_IPC_BATT_PORT_BASE);
799bc289b8SHenning Schild 	release_region(simatic_ipc_batt_io_res.start, resource_size(&simatic_ipc_batt_io_res));
809bc289b8SHenning Schild 
819bc289b8SHenning Schild 	if (val & (1 << 7))
829bc289b8SHenning Schild 		r = SIMATIC_IPC_BATT_LEVEL_EMPTY;
839bc289b8SHenning Schild 	else if (val & (1 << 6))
849bc289b8SHenning Schild 		r = SIMATIC_IPC_BATT_LEVEL_CRIT;
859bc289b8SHenning Schild 
869bc289b8SHenning Schild 	return r;
879bc289b8SHenning Schild }
889bc289b8SHenning Schild 
simatic_ipc_batt_read_value(struct device * dev)899bc289b8SHenning Schild static long simatic_ipc_batt_read_value(struct device *dev)
909bc289b8SHenning Schild {
919bc289b8SHenning Schild 	unsigned long next_update;
929bc289b8SHenning Schild 
939bc289b8SHenning Schild 	next_update = priv.last_updated_jiffies + msecs_to_jiffies(BATT_DELAY_MS);
949bc289b8SHenning Schild 	if (time_after(jiffies, next_update) || !priv.last_updated_jiffies) {
95d0563dd3Sxingtong.wu 		if (priv.devmode == SIMATIC_IPC_DEVICE_227E)
969bc289b8SHenning Schild 			priv.current_state = simatic_ipc_batt_read_io(dev);
97d0563dd3Sxingtong.wu 		else
98d0563dd3Sxingtong.wu 			priv.current_state = simatic_ipc_batt_read_gpio();
99d0563dd3Sxingtong.wu 
1009bc289b8SHenning Schild 		priv.last_updated_jiffies = jiffies;
1019bc289b8SHenning Schild 		if (priv.current_state < SIMATIC_IPC_BATT_LEVEL_FULL)
102d0563dd3Sxingtong.wu 			dev_warn(dev, "CMOS battery needs to be replaced.\n");
1039bc289b8SHenning Schild 	}
1049bc289b8SHenning Schild 
1059bc289b8SHenning Schild 	return priv.current_state;
1069bc289b8SHenning Schild }
1079bc289b8SHenning Schild 
simatic_ipc_batt_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)1089bc289b8SHenning Schild static int simatic_ipc_batt_read(struct device *dev, enum hwmon_sensor_types type,
1099bc289b8SHenning Schild 				 u32 attr, int channel, long *val)
1109bc289b8SHenning Schild {
1119bc289b8SHenning Schild 	switch (attr) {
1129bc289b8SHenning Schild 	case hwmon_in_input:
1139bc289b8SHenning Schild 		*val = simatic_ipc_batt_read_value(dev);
1149bc289b8SHenning Schild 		break;
1159bc289b8SHenning Schild 	case hwmon_in_lcrit:
1169bc289b8SHenning Schild 		*val = SIMATIC_IPC_BATT_LEVEL_CRIT;
1179bc289b8SHenning Schild 		break;
1189bc289b8SHenning Schild 	default:
1199bc289b8SHenning Schild 		return -EOPNOTSUPP;
1209bc289b8SHenning Schild 	}
1219bc289b8SHenning Schild 
1229bc289b8SHenning Schild 	return 0;
1239bc289b8SHenning Schild }
1249bc289b8SHenning Schild 
simatic_ipc_batt_is_visible(const void * data,enum hwmon_sensor_types type,u32 attr,int channel)1259bc289b8SHenning Schild static umode_t simatic_ipc_batt_is_visible(const void *data, enum hwmon_sensor_types type,
1269bc289b8SHenning Schild 					   u32 attr, int channel)
1279bc289b8SHenning Schild {
1289bc289b8SHenning Schild 	if (attr == hwmon_in_input || attr == hwmon_in_lcrit)
1299bc289b8SHenning Schild 		return 0444;
1309bc289b8SHenning Schild 
1319bc289b8SHenning Schild 	return 0;
1329bc289b8SHenning Schild }
1339bc289b8SHenning Schild 
1349bc289b8SHenning Schild static const struct hwmon_ops simatic_ipc_batt_ops = {
1359bc289b8SHenning Schild 	.is_visible = simatic_ipc_batt_is_visible,
1369bc289b8SHenning Schild 	.read = simatic_ipc_batt_read,
1379bc289b8SHenning Schild };
1389bc289b8SHenning Schild 
1399bc289b8SHenning Schild static const struct hwmon_channel_info *simatic_ipc_batt_info[] = {
1409bc289b8SHenning Schild 	HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LCRIT),
1419bc289b8SHenning Schild 	NULL
1429bc289b8SHenning Schild };
1439bc289b8SHenning Schild 
1449bc289b8SHenning Schild static const struct hwmon_chip_info simatic_ipc_batt_chip_info = {
1459bc289b8SHenning Schild 	.ops = &simatic_ipc_batt_ops,
1469bc289b8SHenning Schild 	.info = simatic_ipc_batt_info,
1479bc289b8SHenning Schild };
1489bc289b8SHenning Schild 
simatic_ipc_batt_remove(struct platform_device * pdev,struct gpiod_lookup_table * table)1499bc289b8SHenning Schild int simatic_ipc_batt_remove(struct platform_device *pdev, struct gpiod_lookup_table *table)
1509bc289b8SHenning Schild {
1519bc289b8SHenning Schild 	gpiod_remove_lookup_table(table);
1529bc289b8SHenning Schild 	return 0;
1539bc289b8SHenning Schild }
1549bc289b8SHenning Schild EXPORT_SYMBOL_GPL(simatic_ipc_batt_remove);
1559bc289b8SHenning Schild 
simatic_ipc_batt_probe(struct platform_device * pdev,struct gpiod_lookup_table * table)1569bc289b8SHenning Schild int simatic_ipc_batt_probe(struct platform_device *pdev, struct gpiod_lookup_table *table)
1579bc289b8SHenning Schild {
1589bc289b8SHenning Schild 	struct simatic_ipc_platform *plat;
1599bc289b8SHenning Schild 	struct device *dev = &pdev->dev;
1609bc289b8SHenning Schild 	struct device *hwmon_dev;
161d0563dd3Sxingtong.wu 	unsigned long flags;
1629bc289b8SHenning Schild 	int err;
1639bc289b8SHenning Schild 
1649bc289b8SHenning Schild 	plat = pdev->dev.platform_data;
1659bc289b8SHenning Schild 	priv.devmode = plat->devmode;
1669bc289b8SHenning Schild 
1679bc289b8SHenning Schild 	switch (priv.devmode) {
1689bc289b8SHenning Schild 	case SIMATIC_IPC_DEVICE_127E:
1699bc289b8SHenning Schild 	case SIMATIC_IPC_DEVICE_227G:
1709bc289b8SHenning Schild 	case SIMATIC_IPC_DEVICE_BX_39A:
1719bc289b8SHenning Schild 	case SIMATIC_IPC_DEVICE_BX_21A:
172c56beff2Sxingtong.wu 	case SIMATIC_IPC_DEVICE_BX_59A:
1739bc289b8SHenning Schild 		table->dev_id = dev_name(dev);
1749bc289b8SHenning Schild 		gpiod_add_lookup_table(table);
1759bc289b8SHenning Schild 		break;
1769bc289b8SHenning Schild 	case SIMATIC_IPC_DEVICE_227E:
1779bc289b8SHenning Schild 		goto nogpio;
1789bc289b8SHenning Schild 	default:
1799bc289b8SHenning Schild 		return -ENODEV;
1809bc289b8SHenning Schild 	}
1819bc289b8SHenning Schild 
1829bc289b8SHenning Schild 	priv.gpios[0] = devm_gpiod_get_index(dev, "CMOSBattery empty", 0, GPIOD_IN);
1839bc289b8SHenning Schild 	if (IS_ERR(priv.gpios[0])) {
1849bc289b8SHenning Schild 		err = PTR_ERR(priv.gpios[0]);
1859bc289b8SHenning Schild 		priv.gpios[0] = NULL;
1869bc289b8SHenning Schild 		goto out;
1879bc289b8SHenning Schild 	}
1889bc289b8SHenning Schild 	priv.gpios[1] = devm_gpiod_get_index(dev, "CMOSBattery low", 1, GPIOD_IN);
1899bc289b8SHenning Schild 	if (IS_ERR(priv.gpios[1])) {
1909bc289b8SHenning Schild 		err = PTR_ERR(priv.gpios[1]);
1919bc289b8SHenning Schild 		priv.gpios[1] = NULL;
1929bc289b8SHenning Schild 		goto out;
1939bc289b8SHenning Schild 	}
1949bc289b8SHenning Schild 
1959bc289b8SHenning Schild 	if (table->table[2].key) {
196d0563dd3Sxingtong.wu 		flags = GPIOD_OUT_HIGH;
197*7abf253aSxingtong.wu 		if (priv.devmode == SIMATIC_IPC_DEVICE_BX_21A ||
198*7abf253aSxingtong.wu 		    priv.devmode == SIMATIC_IPC_DEVICE_BX_59A)
199d0563dd3Sxingtong.wu 			flags = GPIOD_OUT_LOW;
200d0563dd3Sxingtong.wu 		priv.gpios[2] = devm_gpiod_get_index(dev, "CMOSBattery meter", 2, flags);
2019bc289b8SHenning Schild 		if (IS_ERR(priv.gpios[2])) {
202e5d5ffa4SYang Yingliang 			err = PTR_ERR(priv.gpios[2]);
2039bc289b8SHenning Schild 			priv.gpios[2] = NULL;
2049bc289b8SHenning Schild 			goto out;
2059bc289b8SHenning Schild 		}
2069bc289b8SHenning Schild 	} else {
2079bc289b8SHenning Schild 		priv.gpios[2] = NULL;
2089bc289b8SHenning Schild 	}
2099bc289b8SHenning Schild 
2109bc289b8SHenning Schild nogpio:
2119bc289b8SHenning Schild 	hwmon_dev = devm_hwmon_device_register_with_info(dev, KBUILD_MODNAME,
2129bc289b8SHenning Schild 							 &priv,
2139bc289b8SHenning Schild 							 &simatic_ipc_batt_chip_info,
2149bc289b8SHenning Schild 							 NULL);
2159bc289b8SHenning Schild 	if (IS_ERR(hwmon_dev)) {
2169bc289b8SHenning Schild 		err = PTR_ERR(hwmon_dev);
2179bc289b8SHenning Schild 		goto out;
2189bc289b8SHenning Schild 	}
2199bc289b8SHenning Schild 
2209bc289b8SHenning Schild 	/* warn about aging battery even if userspace never reads hwmon */
2219bc289b8SHenning Schild 	simatic_ipc_batt_read_value(dev);
2229bc289b8SHenning Schild 
2239bc289b8SHenning Schild 	return 0;
2249bc289b8SHenning Schild out:
2259bc289b8SHenning Schild 	simatic_ipc_batt_remove(pdev, table);
2269bc289b8SHenning Schild 
2279bc289b8SHenning Schild 	return err;
2289bc289b8SHenning Schild }
2299bc289b8SHenning Schild EXPORT_SYMBOL_GPL(simatic_ipc_batt_probe);
2309bc289b8SHenning Schild 
simatic_ipc_batt_io_remove(struct platform_device * pdev)2319bc289b8SHenning Schild static int simatic_ipc_batt_io_remove(struct platform_device *pdev)
2329bc289b8SHenning Schild {
2339bc289b8SHenning Schild 	return simatic_ipc_batt_remove(pdev, NULL);
2349bc289b8SHenning Schild }
2359bc289b8SHenning Schild 
simatic_ipc_batt_io_probe(struct platform_device * pdev)2369bc289b8SHenning Schild static int simatic_ipc_batt_io_probe(struct platform_device *pdev)
2379bc289b8SHenning Schild {
2389bc289b8SHenning Schild 	return simatic_ipc_batt_probe(pdev, NULL);
2399bc289b8SHenning Schild }
2409bc289b8SHenning Schild 
2419bc289b8SHenning Schild static struct platform_driver simatic_ipc_batt_driver = {
2429bc289b8SHenning Schild 	.probe = simatic_ipc_batt_io_probe,
2439bc289b8SHenning Schild 	.remove = simatic_ipc_batt_io_remove,
2449bc289b8SHenning Schild 	.driver = {
2459bc289b8SHenning Schild 		.name = KBUILD_MODNAME,
2469bc289b8SHenning Schild 	},
2479bc289b8SHenning Schild };
2489bc289b8SHenning Schild 
2499bc289b8SHenning Schild module_platform_driver(simatic_ipc_batt_driver);
2509bc289b8SHenning Schild 
2519bc289b8SHenning Schild MODULE_LICENSE("GPL");
2529bc289b8SHenning Schild MODULE_ALIAS("platform:" KBUILD_MODNAME);
2539bc289b8SHenning Schild MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
254