Merge tag '6.6-rc-smb3-client-fixes-part2' of git://git.samba.org/sfrench/cifs-2.6
[sfrench/cifs-2.6.git] / drivers / platform / x86 / siemens / simatic-ipc-batt.c
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 int simatic_ipc_batt_remove(struct platform_device *pdev, struct gpiod_lookup_table *table)
150 {
151         gpiod_remove_lookup_table(table);
152         return 0;
153 }
154 EXPORT_SYMBOL_GPL(simatic_ipc_batt_remove);
155
156 int simatic_ipc_batt_probe(struct platform_device *pdev, struct gpiod_lookup_table *table)
157 {
158         struct simatic_ipc_platform *plat;
159         struct device *dev = &pdev->dev;
160         struct device *hwmon_dev;
161         unsigned long flags;
162         int err;
163
164         plat = pdev->dev.platform_data;
165         priv.devmode = plat->devmode;
166
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);
175                 break;
176         case SIMATIC_IPC_DEVICE_227E:
177                 goto nogpio;
178         default:
179                 return -ENODEV;
180         }
181
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;
186                 goto out;
187         }
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;
192                 goto out;
193         }
194
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;
204                         goto out;
205                 }
206         } else {
207                 priv.gpios[2] = NULL;
208         }
209
210 nogpio:
211         hwmon_dev = devm_hwmon_device_register_with_info(dev, KBUILD_MODNAME,
212                                                          &priv,
213                                                          &simatic_ipc_batt_chip_info,
214                                                          NULL);
215         if (IS_ERR(hwmon_dev)) {
216                 err = PTR_ERR(hwmon_dev);
217                 goto out;
218         }
219
220         /* warn about aging battery even if userspace never reads hwmon */
221         simatic_ipc_batt_read_value(dev);
222
223         return 0;
224 out:
225         simatic_ipc_batt_remove(pdev, table);
226
227         return err;
228 }
229 EXPORT_SYMBOL_GPL(simatic_ipc_batt_probe);
230
231 static int simatic_ipc_batt_io_remove(struct platform_device *pdev)
232 {
233         return simatic_ipc_batt_remove(pdev, NULL);
234 }
235
236 static int simatic_ipc_batt_io_probe(struct platform_device *pdev)
237 {
238         return simatic_ipc_batt_probe(pdev, NULL);
239 }
240
241 static struct platform_driver simatic_ipc_batt_driver = {
242         .probe = simatic_ipc_batt_io_probe,
243         .remove = simatic_ipc_batt_io_remove,
244         .driver = {
245                 .name = KBUILD_MODNAME,
246         },
247 };
248
249 module_platform_driver(simatic_ipc_batt_driver);
250
251 MODULE_LICENSE("GPL");
252 MODULE_ALIAS("platform:" KBUILD_MODNAME);
253 MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");