diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index f3ec24691378..550145f82726 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -56,6 +56,16 @@ config REGULATOR_USERSPACE_CONSUMER If unsure, say no. +config REGULATOR_NETLINK_EVENTS + bool "Enable support for receiving regulator events via netlink" + depends on NET + help + Enabling this option allows the kernel to broadcast regulator events using + the netlink mechanism. User-space applications can subscribe to these events + for real-time updates on various regulator events. + + If unsure, say no. + config REGULATOR_88PG86X tristate "Marvell 88PG86X voltage regulators" depends on I2C diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index b2b059b5ee56..46fb569e6be8 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_REGULATOR) += core.o dummy.o fixed-helper.o helpers.o devres.o irq_helpers.o +obj-$(CONFIG_REGULATOR_NETLINK_EVENTS) += event.o obj-$(CONFIG_OF) += of_regulator.o obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 4aa9ec8c22f3..a968dabb48f5 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -33,6 +33,7 @@ #include "dummy.h" #include "internal.h" +#include "regnl.h" static DEFINE_WW_CLASS(regulator_ww_class); static DEFINE_MUTEX(regulator_nesting_mutex); @@ -4854,7 +4855,23 @@ static int _notifier_call_chain(struct regulator_dev *rdev, unsigned long event, void *data) { /* call rdev chain first */ - return blocking_notifier_call_chain(&rdev->notifier, event, data); + int ret = blocking_notifier_call_chain(&rdev->notifier, event, data); + + if (IS_REACHABLE(CONFIG_REGULATOR_NETLINK_EVENTS)) { + struct device *parent = rdev->dev.parent; + const char *rname = rdev_get_name(rdev); + char name[32]; + + /* Avoid duplicate debugfs directory names */ + if (parent && rname == rdev->desc->name) { + snprintf(name, sizeof(name), "%s-%s", dev_name(parent), + rname); + rname = name; + } + reg_generate_netlink_event(rname, event); + } + + return ret; } int _regulator_bulk_get(struct device *dev, int num_consumers, diff --git a/drivers/regulator/event.c b/drivers/regulator/event.c new file mode 100644 index 000000000000..0ec58f306b38 --- /dev/null +++ b/drivers/regulator/event.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Regulator event over netlink + * + * Author: Naresh Solanki + */ + +#include +#include +#include + +#include "regnl.h" + +static unsigned int reg_event_seqnum; + +static const struct genl_multicast_group reg_event_mcgrps[] = { + { .name = REG_GENL_MCAST_GROUP_NAME, }, +}; + +static struct genl_family reg_event_genl_family __ro_after_init = { + .module = THIS_MODULE, + .name = REG_GENL_FAMILY_NAME, + .version = REG_GENL_VERSION, + .maxattr = REG_GENL_ATTR_MAX, + .mcgrps = reg_event_mcgrps, + .n_mcgrps = ARRAY_SIZE(reg_event_mcgrps), +}; + +int reg_generate_netlink_event(const char *reg_name, u64 event) +{ + struct sk_buff *skb; + struct nlattr *attr; + struct reg_genl_event *edata; + void *msg_header; + int size; + + /* allocate memory */ + size = nla_total_size(sizeof(struct reg_genl_event)) + + nla_total_size(0); + + skb = genlmsg_new(size, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + /* add the genetlink message header */ + msg_header = genlmsg_put(skb, 0, reg_event_seqnum++, + ®_event_genl_family, 0, + REG_GENL_CMD_EVENT); + if (!msg_header) { + nlmsg_free(skb); + return -ENOMEM; + } + + /* fill the data */ + attr = nla_reserve(skb, REG_GENL_ATTR_EVENT, sizeof(struct reg_genl_event)); + if (!attr) { + nlmsg_free(skb); + return -EINVAL; + } + + edata = nla_data(attr); + memset(edata, 0, sizeof(struct reg_genl_event)); + + strscpy(edata->reg_name, reg_name, sizeof(edata->reg_name)); + edata->event = event; + + /* send multicast genetlink message */ + genlmsg_end(skb, msg_header); + size = genlmsg_multicast(®_event_genl_family, skb, 0, 0, GFP_ATOMIC); + + return size; +} + +static int __init reg_event_genetlink_init(void) +{ + return genl_register_family(®_event_genl_family); +} + +static int __init reg_event_init(void) +{ + int error; + + /* create genetlink for acpi event */ + error = reg_event_genetlink_init(); + if (error) + pr_warn("Failed to create genetlink family for reg event\n"); + + return 0; +} + +fs_initcall(reg_event_init); diff --git a/drivers/regulator/regnl.h b/drivers/regulator/regnl.h new file mode 100644 index 000000000000..bcba16cc05cc --- /dev/null +++ b/drivers/regulator/regnl.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Regulator event over netlink + * + * Author: Naresh Solanki + */ + +#ifndef __REGULATOR_EVENT_H +#define __REGULATOR_EVENT_H + +int reg_generate_netlink_event(const char *reg_name, u64 event); + +#endif diff --git a/include/linux/regulator/consumer.h b/include/linux/regulator/consumer.h index 39b666b40ea6..4660582a3302 100644 --- a/include/linux/regulator/consumer.h +++ b/include/linux/regulator/consumer.h @@ -33,6 +33,7 @@ #include #include +#include struct device; struct notifier_block; @@ -84,52 +85,6 @@ struct regulator_dev; #define REGULATOR_MODE_IDLE 0x4 #define REGULATOR_MODE_STANDBY 0x8 -/* - * Regulator notifier events. - * - * UNDER_VOLTAGE Regulator output is under voltage. - * OVER_CURRENT Regulator output current is too high. - * REGULATION_OUT Regulator output is out of regulation. - * FAIL Regulator output has failed. - * OVER_TEMP Regulator over temp. - * FORCE_DISABLE Regulator forcibly shut down by software. - * VOLTAGE_CHANGE Regulator voltage changed. - * Data passed is old voltage cast to (void *). - * DISABLE Regulator was disabled. - * PRE_VOLTAGE_CHANGE Regulator is about to have voltage changed. - * Data passed is "struct pre_voltage_change_data" - * ABORT_VOLTAGE_CHANGE Regulator voltage change failed for some reason. - * Data passed is old voltage cast to (void *). - * PRE_DISABLE Regulator is about to be disabled - * ABORT_DISABLE Regulator disable failed for some reason - * - * NOTE: These events can be OR'ed together when passed into handler. - */ - -#define REGULATOR_EVENT_UNDER_VOLTAGE 0x01 -#define REGULATOR_EVENT_OVER_CURRENT 0x02 -#define REGULATOR_EVENT_REGULATION_OUT 0x04 -#define REGULATOR_EVENT_FAIL 0x08 -#define REGULATOR_EVENT_OVER_TEMP 0x10 -#define REGULATOR_EVENT_FORCE_DISABLE 0x20 -#define REGULATOR_EVENT_VOLTAGE_CHANGE 0x40 -#define REGULATOR_EVENT_DISABLE 0x80 -#define REGULATOR_EVENT_PRE_VOLTAGE_CHANGE 0x100 -#define REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE 0x200 -#define REGULATOR_EVENT_PRE_DISABLE 0x400 -#define REGULATOR_EVENT_ABORT_DISABLE 0x800 -#define REGULATOR_EVENT_ENABLE 0x1000 -/* - * Following notifications should be emitted only if detected condition - * is such that the HW is likely to still be working but consumers should - * take a recovery action to prevent problems esacalating into errors. - */ -#define REGULATOR_EVENT_UNDER_VOLTAGE_WARN 0x2000 -#define REGULATOR_EVENT_OVER_CURRENT_WARN 0x4000 -#define REGULATOR_EVENT_OVER_VOLTAGE_WARN 0x8000 -#define REGULATOR_EVENT_OVER_TEMP_WARN 0x10000 -#define REGULATOR_EVENT_WARN_MASK 0x1E000 - /* * Regulator errors that can be queried using regulator_get_error_flags * diff --git a/include/uapi/regulator/regulator.h b/include/uapi/regulator/regulator.h new file mode 100644 index 000000000000..d2b5612198b6 --- /dev/null +++ b/include/uapi/regulator/regulator.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Regulator uapi header + * + * Author: Naresh Solanki + */ + +#ifndef _UAPI_REGULATOR_H +#define _UAPI_REGULATOR_H + +#ifdef __KERNEL__ +#include +#else +#include +#endif + +/* + * Regulator notifier events. + * + * UNDER_VOLTAGE Regulator output is under voltage. + * OVER_CURRENT Regulator output current is too high. + * REGULATION_OUT Regulator output is out of regulation. + * FAIL Regulator output has failed. + * OVER_TEMP Regulator over temp. + * FORCE_DISABLE Regulator forcibly shut down by software. + * VOLTAGE_CHANGE Regulator voltage changed. + * Data passed is old voltage cast to (void *). + * DISABLE Regulator was disabled. + * PRE_VOLTAGE_CHANGE Regulator is about to have voltage changed. + * Data passed is "struct pre_voltage_change_data" + * ABORT_VOLTAGE_CHANGE Regulator voltage change failed for some reason. + * Data passed is old voltage cast to (void *). + * PRE_DISABLE Regulator is about to be disabled + * ABORT_DISABLE Regulator disable failed for some reason + * + * NOTE: These events can be OR'ed together when passed into handler. + */ + +#define REGULATOR_EVENT_UNDER_VOLTAGE 0x01 +#define REGULATOR_EVENT_OVER_CURRENT 0x02 +#define REGULATOR_EVENT_REGULATION_OUT 0x04 +#define REGULATOR_EVENT_FAIL 0x08 +#define REGULATOR_EVENT_OVER_TEMP 0x10 +#define REGULATOR_EVENT_FORCE_DISABLE 0x20 +#define REGULATOR_EVENT_VOLTAGE_CHANGE 0x40 +#define REGULATOR_EVENT_DISABLE 0x80 +#define REGULATOR_EVENT_PRE_VOLTAGE_CHANGE 0x100 +#define REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE 0x200 +#define REGULATOR_EVENT_PRE_DISABLE 0x400 +#define REGULATOR_EVENT_ABORT_DISABLE 0x800 +#define REGULATOR_EVENT_ENABLE 0x1000 +/* + * Following notifications should be emitted only if detected condition + * is such that the HW is likely to still be working but consumers should + * take a recovery action to prevent problems esacalating into errors. + */ +#define REGULATOR_EVENT_UNDER_VOLTAGE_WARN 0x2000 +#define REGULATOR_EVENT_OVER_CURRENT_WARN 0x4000 +#define REGULATOR_EVENT_OVER_VOLTAGE_WARN 0x8000 +#define REGULATOR_EVENT_OVER_TEMP_WARN 0x10000 +#define REGULATOR_EVENT_WARN_MASK 0x1E000 + +struct reg_genl_event { + char reg_name[32]; + uint64_t event; +}; + +/* attributes of reg_genl_family */ +enum { + REG_GENL_ATTR_UNSPEC, + REG_GENL_ATTR_EVENT, /* reg event info needed by user space */ + __REG_GENL_ATTR_MAX, +}; + +#define REG_GENL_ATTR_MAX (__REG_GENL_ATTR_MAX - 1) + +/* commands supported by the reg_genl_family */ +enum { + REG_GENL_CMD_UNSPEC, + REG_GENL_CMD_EVENT, /* kernel->user notifications for reg events */ + __REG_GENL_CMD_MAX, +}; + +#define REG_GENL_CMD_MAX (__REG_GENL_CMD_MAX - 1) + +#define REG_GENL_FAMILY_NAME "reg_event" +#define REG_GENL_VERSION 0x01 +#define REG_GENL_MCAST_GROUP_NAME "reg_mc_group" + +#endif /* _UAPI_REGULATOR_H */