xref: /linux/drivers/platform/x86/siemens/simatic-ipc-batt.c (revision 4b660dbd9ee2059850fd30e0df420ca7a38a1856)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Siemens SIMATIC IPC driver for CMOS battery monitoring
4  *
5  * Copyright (c) Siemens AG, 2023
6  *
7  * Authors:
8  *  Gerd Haeussler <gerd.haeussler.ext@siemens.com>
9  *  Henning Schild <henning.schild@siemens.com>
10  */
11 
12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13 
14 #include <linux/delay.h>
15 #include <linux/io.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>
27 
28 #include "simatic-ipc-batt.h"
29 
30 #define BATT_DELAY_MS	(1000 * 60 * 60 * 24)	/* 24 h delay */
31 
32 #define SIMATIC_IPC_BATT_LEVEL_FULL	3000
33 #define SIMATIC_IPC_BATT_LEVEL_CRIT	2750
34 #define SIMATIC_IPC_BATT_LEVEL_EMPTY	   0
35 
36 static struct simatic_ipc_batt {
37 	u8 devmode;
38 	long current_state;
39 	struct gpio_desc *gpios[3];
40 	unsigned long last_updated_jiffies;
41 } priv;
42 
43 static long simatic_ipc_batt_read_gpio(void)
44 {
45 	long r = SIMATIC_IPC_BATT_LEVEL_FULL;
46 
47 	if (priv.gpios[2]) {
48 		gpiod_set_value(priv.gpios[2], 1);
49 		msleep(150);
50 	}
51 
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;
56 
57 	if (priv.gpios[2])
58 		gpiod_set_value(priv.gpios[2], 0);
59 
60 	return r;
61 }
62 
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);
66 
67 static long simatic_ipc_batt_read_io(struct device *dev)
68 {
69 	long r = SIMATIC_IPC_BATT_LEVEL_FULL;
70 	struct resource *res = &simatic_ipc_batt_io_res;
71 	u8 val;
72 
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);
75 		return -EBUSY;
76 	}
77 
78 	val = inb(SIMATIC_IPC_BATT_PORT_BASE);
79 	release_region(simatic_ipc_batt_io_res.start, resource_size(&simatic_ipc_batt_io_res));
80 
81 	if (val & (1 << 7))
82 		r = SIMATIC_IPC_BATT_LEVEL_EMPTY;
83 	else if (val & (1 << 6))
84 		r = SIMATIC_IPC_BATT_LEVEL_CRIT;
85 
86 	return r;
87 }
88 
89 static long simatic_ipc_batt_read_value(struct device *dev)
90 {
91 	unsigned long next_update;
92 
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);
97 		else
98 			priv.current_state = simatic_ipc_batt_read_gpio();
99 
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");
103 	}
104 
105 	return priv.current_state;
106 }
107 
108 static int simatic_ipc_batt_read(struct device *dev, enum hwmon_sensor_types type,
109 				 u32 attr, int channel, long *val)
110 {
111 	switch (attr) {
112 	case hwmon_in_input:
113 		*val = simatic_ipc_batt_read_value(dev);
114 		break;
115 	case hwmon_in_lcrit:
116 		*val = SIMATIC_IPC_BATT_LEVEL_CRIT;
117 		break;
118 	default:
119 		return -EOPNOTSUPP;
120 	}
121 
122 	return 0;
123 }
124 
125 static umode_t simatic_ipc_batt_is_visible(const void *data, enum hwmon_sensor_types type,
126 					   u32 attr, int channel)
127 {
128 	if (attr == hwmon_in_input || attr == hwmon_in_lcrit)
129 		return 0444;
130 
131 	return 0;
132 }
133 
134 static const struct hwmon_ops simatic_ipc_batt_ops = {
135 	.is_visible = simatic_ipc_batt_is_visible,
136 	.read = simatic_ipc_batt_read,
137 };
138 
139 static const struct hwmon_channel_info *simatic_ipc_batt_info[] = {
140 	HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LCRIT),
141 	NULL
142 };
143 
144 static const struct hwmon_chip_info simatic_ipc_batt_chip_info = {
145 	.ops = &simatic_ipc_batt_ops,
146 	.info = simatic_ipc_batt_info,
147 };
148 
149 void simatic_ipc_batt_remove(struct platform_device *pdev, struct gpiod_lookup_table *table)
150 {
151 	gpiod_remove_lookup_table(table);
152 }
153 EXPORT_SYMBOL_GPL(simatic_ipc_batt_remove);
154 
155 int simatic_ipc_batt_probe(struct platform_device *pdev, struct gpiod_lookup_table *table)
156 {
157 	struct simatic_ipc_platform *plat;
158 	struct device *dev = &pdev->dev;
159 	struct device *hwmon_dev;
160 	unsigned long flags;
161 	int err;
162 
163 	plat = pdev->dev.platform_data;
164 	priv.devmode = plat->devmode;
165 
166 	switch (priv.devmode) {
167 	case SIMATIC_IPC_DEVICE_127E:
168 	case SIMATIC_IPC_DEVICE_227G:
169 	case SIMATIC_IPC_DEVICE_BX_39A:
170 	case SIMATIC_IPC_DEVICE_BX_21A:
171 	case SIMATIC_IPC_DEVICE_BX_59A:
172 		table->dev_id = dev_name(dev);
173 		gpiod_add_lookup_table(table);
174 		break;
175 	case SIMATIC_IPC_DEVICE_227E:
176 		goto nogpio;
177 	default:
178 		return -ENODEV;
179 	}
180 
181 	priv.gpios[0] = devm_gpiod_get_index(dev, "CMOSBattery empty", 0, GPIOD_IN);
182 	if (IS_ERR(priv.gpios[0])) {
183 		err = PTR_ERR(priv.gpios[0]);
184 		priv.gpios[0] = NULL;
185 		goto out;
186 	}
187 	priv.gpios[1] = devm_gpiod_get_index(dev, "CMOSBattery low", 1, GPIOD_IN);
188 	if (IS_ERR(priv.gpios[1])) {
189 		err = PTR_ERR(priv.gpios[1]);
190 		priv.gpios[1] = NULL;
191 		goto out;
192 	}
193 
194 	if (table->table[2].key) {
195 		flags = GPIOD_OUT_HIGH;
196 		if (priv.devmode == SIMATIC_IPC_DEVICE_BX_21A ||
197 		    priv.devmode == SIMATIC_IPC_DEVICE_BX_59A)
198 			flags = GPIOD_OUT_LOW;
199 		priv.gpios[2] = devm_gpiod_get_index(dev, "CMOSBattery meter", 2, flags);
200 		if (IS_ERR(priv.gpios[2])) {
201 			err = PTR_ERR(priv.gpios[2]);
202 			priv.gpios[2] = NULL;
203 			goto out;
204 		}
205 	} else {
206 		priv.gpios[2] = NULL;
207 	}
208 
209 nogpio:
210 	hwmon_dev = devm_hwmon_device_register_with_info(dev, KBUILD_MODNAME,
211 							 &priv,
212 							 &simatic_ipc_batt_chip_info,
213 							 NULL);
214 	if (IS_ERR(hwmon_dev)) {
215 		err = PTR_ERR(hwmon_dev);
216 		goto out;
217 	}
218 
219 	/* warn about aging battery even if userspace never reads hwmon */
220 	simatic_ipc_batt_read_value(dev);
221 
222 	return 0;
223 out:
224 	simatic_ipc_batt_remove(pdev, table);
225 
226 	return err;
227 }
228 EXPORT_SYMBOL_GPL(simatic_ipc_batt_probe);
229 
230 static void simatic_ipc_batt_io_remove(struct platform_device *pdev)
231 {
232 	simatic_ipc_batt_remove(pdev, NULL);
233 }
234 
235 static int simatic_ipc_batt_io_probe(struct platform_device *pdev)
236 {
237 	return simatic_ipc_batt_probe(pdev, NULL);
238 }
239 
240 static struct platform_driver simatic_ipc_batt_driver = {
241 	.probe = simatic_ipc_batt_io_probe,
242 	.remove_new = simatic_ipc_batt_io_remove,
243 	.driver = {
244 		.name = KBUILD_MODNAME,
245 	},
246 };
247 
248 module_platform_driver(simatic_ipc_batt_driver);
249 
250 MODULE_LICENSE("GPL");
251 MODULE_ALIAS("platform:" KBUILD_MODNAME);
252 MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
253