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>"); 254