Linux Audio

Check our new training course

Loading...
Note: File does not exist in v6.2.
  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
 36static 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
 43static 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
 64static struct resource simatic_ipc_batt_io_res =
 65	DEFINE_RES_IO_NAMED(SIMATIC_IPC_BATT_PORT_BASE, SZ_1, KBUILD_MODNAME);
 66
 67static 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
 89static 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
108static 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
125static 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
134static const struct hwmon_ops simatic_ipc_batt_ops = {
135	.is_visible = simatic_ipc_batt_is_visible,
136	.read = simatic_ipc_batt_read,
137};
138
139static const struct hwmon_channel_info *simatic_ipc_batt_info[] = {
140	HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LCRIT),
141	NULL
142};
143
144static const struct hwmon_chip_info simatic_ipc_batt_chip_info = {
145	.ops = &simatic_ipc_batt_ops,
146	.info = simatic_ipc_batt_info,
147};
148
149void simatic_ipc_batt_remove(struct platform_device *pdev, struct gpiod_lookup_table *table)
150{
151	gpiod_remove_lookup_table(table);
152}
153EXPORT_SYMBOL_GPL(simatic_ipc_batt_remove);
154
155int 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
209nogpio:
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;
223out:
224	simatic_ipc_batt_remove(pdev, table);
225
226	return err;
227}
228EXPORT_SYMBOL_GPL(simatic_ipc_batt_probe);
229
230static void simatic_ipc_batt_io_remove(struct platform_device *pdev)
231{
232	simatic_ipc_batt_remove(pdev, NULL);
233}
234
235static int simatic_ipc_batt_io_probe(struct platform_device *pdev)
236{
237	return simatic_ipc_batt_probe(pdev, NULL);
238}
239
240static 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
248module_platform_driver(simatic_ipc_batt_driver);
249
250MODULE_LICENSE("GPL");
251MODULE_ALIAS("platform:" KBUILD_MODNAME);
252MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");