@@ -88,4 +88,6 @@ config ARM_EXYNOS5_BUS_DEVFREQ
It reads PPMU counters of memory controllers and adjusts the
operating frequencies and voltages with OPP support.
+comment "DEVFREQ Event Drivers"
+
endif # PM_DEVFREQ
@@ -1,4 +1,4 @@
-obj-$(CONFIG_PM_DEVFREQ) += devfreq.o
+obj-$(CONFIG_PM_DEVFREQ) += devfreq.o devfreq-event.o
obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) += governor_simpleondemand.o
obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o
obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o
@@ -7,3 +7,6 @@ obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
# DEVFREQ Drivers
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/
obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/
+
+# DEVFREQ Event Drivers
+obj-$(CONFIG_PM_DEVFREQ) += event/
new file mode 100644
@@ -0,0 +1,251 @@
+/*
+ * devfreq-event: Generic DEVFREQ Event class driver
+ *
+ * Copyright (C) 2014 Samsung Electronics
+ * Chanwoo Choi <cw00.choi@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This driver is based on drivers/devfreq/devfreq.c
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/pm_opp.h>
+#include <linux/devfreq.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/list.h>
+#include <linux/printk.h>
+#include <linux/hrtimer.h>
+#include <linux/of.h>
+#include "governor.h"
+
+static struct class *devfreq_event_class;
+
+/* The list of all devfreq event list */
+static LIST_HEAD(devfreq_event_list);
+static DEFINE_MUTEX(devfreq_event_list_lock);
+
+#define to_devfreq_event(DEV) container_of(DEV, struct devfreq_event_dev, dev)
+
+struct devfreq_event_dev *devfreq_add_event_device(struct device *dev,
+ struct devfreq_event_desc *desc)
+{
+ struct devfreq_event_dev *edev;
+ static atomic_t event_no = ATOMIC_INIT(0);
+ int ret;
+
+ if (!dev || !desc)
+ return ERR_PTR(-EINVAL);
+
+ if (!desc->name || !desc->ops || !desc->dev)
+ return ERR_PTR(-EINVAL);
+
+ edev = kzalloc(sizeof(struct devfreq_event_dev), GFP_KERNEL);
+ if (!edev)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_lock(&devfreq_event_list_lock);
+
+ mutex_init(&edev->mutex);
+ edev->desc = desc;
+ edev->driver_data = desc->driver_data;
+ edev->owner = desc->owner;
+
+ edev->dev.parent = dev;
+ edev->dev.class = devfreq_event_class;
+
+ dev_set_name(&edev->dev, "event.%d",
+ atomic_inc_return(&event_no) - 1);
+ ret = device_register(&edev->dev);
+ if (ret != 0) {
+ put_device(&edev->dev);
+ goto err;
+ }
+ dev_set_drvdata(&edev->dev, edev);
+
+ /* Add devfreq event device to devfreq_event_list */
+ INIT_LIST_HEAD(&edev->node);
+ list_add(&edev->node, &devfreq_event_list);
+
+ mutex_unlock(&devfreq_event_list_lock);
+
+ return edev;
+err:
+ kfree(edev);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(devfreq_add_event_device);
+
+struct devfreq_event_dev *devfreq_get_edev(const char *edev_name)
+{
+ struct devfreq_event_dev *edev;
+
+ mutex_lock(&devfreq_event_list_lock);
+ list_for_each_entry(edev, &devfreq_event_list, node) {
+ if (!strcmp(edev->desc->name, edev_name))
+ goto out;
+ }
+ edev = NULL;
+out:
+ mutex_unlock(&devfreq_event_list_lock);
+
+ return edev;
+}
+EXPORT_SYMBOL_GPL(devfreq_get_edev);
+
+struct devfreq_event_dev *devfreq_get_edev_by_phandle(struct device *dev,
+ int index)
+{
+ struct device_node *node;
+ struct devfreq_event_dev *edev;
+
+ if (!dev->of_node) {
+ dev_err(dev, "device does not have a device node entry\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ node = of_parse_phandle(dev->of_node, "devfreq-events", index);
+ if (!node) {
+ dev_err(dev, "failed to get phandle in %s node\n",
+ dev->of_node->full_name);
+ return ERR_PTR(-ENODEV);
+ }
+
+ edev = devfreq_get_edev(node->name);
+ if (!edev) {
+ dev_err(dev, "unable to get devfreq-event device : %s\n",
+ node->name);
+ return ERR_PTR(-ENODEV);
+ }
+
+ return edev;
+}
+EXPORT_SYMBOL_GPL(devfreq_get_edev_by_phandle);
+
+int devfreq_put_edev(struct devfreq_event_dev *edev)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devfreq_put_edev);
+
+int devfreq_enable_edev(struct devfreq_event_dev *edev)
+{
+ if (!edev || !edev->desc)
+ return -EINVAL;
+
+ if (edev->desc->ops && edev->desc->ops->enable)
+ return edev->desc->ops->enable(edev);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(devfreq_enable_edev);
+
+int devfreq_disable_edev(struct devfreq_event_dev *edev)
+{
+ if (!edev || !edev->desc)
+ return -EINVAL;
+
+ if (edev->desc->ops && edev->desc->ops->disable)
+ return edev->desc->ops->disable(edev);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(devfreq_disable_edev);
+
+int devfreq_set_event_edev(struct devfreq_event_dev *edev)
+{
+ if (!edev || !edev->desc)
+ return -EINVAL;
+
+ if (edev->desc->ops && edev->desc->ops->set_event)
+ return edev->desc->ops->set_event(edev);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(devfreq_set_event_edev);
+
+int devfreq_get_event_edev(struct devfreq_event_dev *edev)
+{
+ if (!edev || !edev->desc)
+ return -EINVAL;
+
+ if (edev->desc->ops && edev->desc->ops->get_event)
+ return edev->desc->ops->get_event(edev);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(devfreq_get_event_edev);
+
+int devfreq_reset_edev(struct devfreq_event_dev *edev)
+{
+ if (!edev || !edev->desc)
+ return -EINVAL;
+
+ if (edev->desc->ops && edev->desc->ops->reset)
+ return edev->desc->ops->reset(edev);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(devfreq_reset_edev);
+
+void *edev_get_drvdata(struct devfreq_event_dev *edev)
+{
+ return edev->driver_data;
+}
+EXPORT_SYMBOL_GPL(edev_get_drvdata);
+
+/*
+ * Device Attribues for devfreq event class
+ */
+static ssize_t name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct devfreq_event_dev *edev = to_devfreq_event(dev);
+
+ if (!edev || !edev->desc)
+ return -EINVAL;
+
+ return sprintf(buf, "%s\n", edev->desc->name);
+}
+static DEVICE_ATTR_RO(name);
+
+static struct attribute *devfreq_event_attrs[] = {
+ &dev_attr_name.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(devfreq_event);
+
+static int __init devfreq_event_init(void)
+{
+ devfreq_event_class = class_create(THIS_MODULE, "devfreq_event");
+ if (IS_ERR(devfreq_event_class)) {
+ pr_err("%s: couldn't create class\n", __FILE__);
+ return PTR_ERR(devfreq_event_class);
+ }
+
+ devfreq_event_class->dev_groups = devfreq_event_groups;
+
+ return 0;
+}
+subsys_initcall(devfreq_event_init);
+
+static void __exit devfreq_event_exit(void)
+{
+ class_destroy(devfreq_event_class);
+}
+module_exit(devfreq_event_exit);
+
+MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
+MODULE_DESCRIPTION("devfreq-event class support");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1 @@
+# Exynos DEVFREQ Event Drivers
@@ -175,6 +175,36 @@ struct devfreq {
unsigned long last_stat_updated;
};
+struct devfreq_event_dev {
+ const struct devfreq_event_desc *desc;
+
+ struct list_head node;
+ struct device dev;
+ struct module *owner;
+ struct mutex mutex;
+
+ void *driver_data;
+};
+
+struct devfreq_event_ops {
+ int (*enable)(struct devfreq_event_dev *edev);
+ int (*disable)(struct devfreq_event_dev *edev);
+ int (*set_event)(struct devfreq_event_dev *edev);
+ int (*get_event)(struct devfreq_event_dev *edev);
+ int (*reset)(struct devfreq_event_dev *edev);
+};
+
+struct devfreq_event_desc {
+ const char *name;
+ unsigned int id;
+
+ struct devfreq_event_ops *ops;
+ struct device *dev;
+ struct module *owner;
+
+ void *driver_data;
+};
+
#if defined(CONFIG_PM_DEVFREQ)
extern struct devfreq *devfreq_add_device(struct device *dev,
struct devfreq_dev_profile *profile,
@@ -204,6 +234,20 @@ extern int devm_devfreq_register_opp_notifier(struct device *dev,
extern void devm_devfreq_unregister_opp_notifier(struct device *dev,
struct devfreq *devfreq);
+/* Functions for devfreq event device */
+extern struct devfreq_event_dev *devfreq_add_event_device(struct device *dev,
+ struct devfreq_event_desc *desc);
+extern struct devfreq_event_dev *devfreq_get_edev(const char *name);
+extern struct devfreq_event_dev *devfreq_get_edev_by_phandle(struct device *dev,
+ int index);
+extern int devfreq_put_edev(struct devfreq_event_dev *edev);
+extern int devfreq_enable_edev(struct devfreq_event_dev *edev);
+extern int devfreq_disable_edev(struct devfreq_event_dev *edev);
+extern int devfreq_set_event_edev(struct devfreq_event_dev *edev);
+extern int devfreq_get_event_edev(struct devfreq_event_dev *edev);
+extern int devfreq_reset_edev(struct devfreq_event_dev *edev);
+extern void *edev_get_drvdata(struct devfreq_event_dev *edev);
+
#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND)
/**
* struct devfreq_simple_ondemand_data - void *data fed to struct devfreq
@@ -233,6 +277,13 @@ static inline struct devfreq *devfreq_add_device(struct device *dev,
return ERR_PTR(-ENOSYS);
}
+static inline struct devfreq_event_dev *devfreq_add_event_device(
+ struct device *dev,
+ struct devfreq_event_desc *desc);
+{
+ return ERR_PTR(-ENODEV);
+}
+
static inline int devfreq_remove_device(struct devfreq *devfreq)
{
return 0;
@@ -289,6 +340,52 @@ static inline void devm_devfreq_unregister_opp_notifier(struct device *dev,
struct devfreq *devfreq)
{
}
+
+static inline void *edev_get_drvdata(struct devfreq_event_dev *edev);
+{
+ return ERR_PTR(-EINVAL);
+}
+
+static inline struct devfreq_event_dev *devfreq_get_edev(const char *name)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+struct devfreq_event_dev *devfreq_get_edev_by_phandle(struct device *dev,
+ int index)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline int devfreq_put_edev(struct devfreq_event_dev *edev)
+{
+ return -EINVAL;
+}
+
+static inline int devfreq_enable_edev(struct devfreq_event_dev *edev)
+{
+ return -EINVAL;
+}
+
+static inline int devfreq_disable_edev(struct devfreq_event_dev *edev)
+{
+ return -EINVAL;
+}
+
+static inline int devfreq_set_event_edev(struct devfreq_event_dev *edev)
+{
+ return -EINVAL;
+}
+
+static inline int devfreq_get_event_edev(struct devfreq_event_dev *edev)
+{
+ return -EINVAL;
+}
+
+static inline int devfreq_reset_edev(struct devfreq_event_dev *edev)
+{
+ return -EINVAL;
+}
#endif /* CONFIG_PM_DEVFREQ */
#endif /* __LINUX_DEVFREQ_H__ */