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