brintos

brintos / linux-shallow public Read only

0
0
Text · 6.2 KiB · d9aff10 Raw
254 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Siemens SIMATIC IPC driver for CMOS battery monitoring4 *5 * Copyright (c) Siemens AG, 20236 *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 ": " fmt13 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	300033#define SIMATIC_IPC_BATT_LEVEL_CRIT	275034#define SIMATIC_IPC_BATT_LEVEL_EMPTY	   035 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	0x404D64static 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		else98			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	NULL142};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_DESCRIPTION("CMOS core battery driver for Siemens Simatic IPCs");251MODULE_LICENSE("GPL");252MODULE_ALIAS("platform:" KBUILD_MODNAME);253MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");254