balena-allwinner/layers/meta-balena-allwinner/recipes-kernel/linux/linux-mainline-4.19/0000-0048-misc-tbs-a711-Power-manager-and-wakeup-monitoring-dr.patch
Vicentiu Galanopulo 065936bc48 recipes-kernel/linux: Rename linux to linux-mainline
Since the Armbian sources refer to linux 4.19.76 as
being linux-mainline, all the patches from linux-4.19
were moved to linux-mainline_4.19 and the linux_4.19.76
recipes was named accordingly.

This patch makes 4.19.76 as default version for linux

Signed-off-by: Vicentiu Galanopulo <vicentiu@balena.io>
2019-10-10 11:50:45 +02:00

544 lines
12 KiB
Diff

From 050ae3acc34834c56ee0a95ed2e96143e4f885b5 Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megous@megous.com>
Date: Sun, 12 Nov 2017 02:10:15 +0100
Subject: [PATCH 48/82] misc: tbs-a711: Power manager and wakeup monitoring
driver
This driver allows for powering/resetting devices that are otherwise
handled by other subsytems (USB). It also has mechanismsm for polling
from userspace on device->SoC wakeup events via GPIO.
Signed-off-by: Ondrej Jirman <megous@megous.com>
---
drivers/misc/Makefile | 1 +
drivers/misc/tbs-a711.c | 509 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 510 insertions(+)
create mode 100644 drivers/misc/tbs-a711.c
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index af22bbc3d00c..4b09a5b406e0 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -58,3 +58,4 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o
obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
obj-$(CONFIG_OCXL) += ocxl/
obj-$(CONFIG_MISC_RTSX) += cardreader/
+obj-y += tbs-a711.o
diff --git a/drivers/misc/tbs-a711.c b/drivers/misc/tbs-a711.c
new file mode 100644
index 000000000000..05fe2ed76429
--- /dev/null
+++ b/drivers/misc/tbs-a711.c
@@ -0,0 +1,509 @@
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#define DRIVER_NAME "tbs_a711"
+
+#define A711_IOCTL_RESET _IO('A', 0)
+#define A711_IOCTL_POWERUP _IO('A', 1)
+#define A711_IOCTL_POWERDN _IO('A', 2)
+#define A711_IOCTL_STATUS _IOR('A', 3, int)
+
+enum {
+ A711_REQ_NONE = 0,
+ A711_REQ_RESET,
+ A711_REQ_PWDN,
+ A711_REQ_PWUP,
+};
+
+struct a711_dev {
+ struct device *dev;
+
+ struct gpio_desc *enable_gpio;
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *wakeup_gpio;
+ struct regulator *regulator;
+ int wakeup_irq;
+ u32 reset_duration;
+ struct cdev cdev;
+ dev_t major;
+
+ // change
+ spinlock_t lock;
+ wait_queue_head_t waitqueue;
+ int got_wakeup;
+ int is_open;
+ struct work_struct work;
+ int last_request;
+ int is_enabled;
+};
+
+static void a711_reset(struct a711_dev* a711)
+{
+ struct device *dev = a711->dev;
+
+ if (!a711->is_enabled)
+ return;
+
+ if (!a711->reset_gpio) {
+ dev_err(dev, "reset is not configured for this device");
+ return;
+ }
+
+ dev_info(dev, "resetting");
+
+ gpiod_set_value(a711->reset_gpio, 1);
+ msleep(a711->reset_duration);
+ gpiod_set_value(a711->reset_gpio, 0);
+}
+
+static void a711_power_down(struct a711_dev* a711)
+{
+ struct device *dev = a711->dev;
+
+ if (!a711->is_enabled)
+ return;
+
+ dev_info(dev, "powering down");
+
+ gpiod_set_value(a711->enable_gpio, 0);
+ if (a711->regulator)
+ regulator_disable(a711->regulator);
+ else
+ gpiod_set_value(a711->reset_gpio, 1);
+ a711->is_enabled = 0;
+}
+
+static void a711_power_up(struct a711_dev* a711)
+{
+ struct device *dev = a711->dev;
+ int ret;
+
+ if (a711->is_enabled)
+ return;
+
+ dev_info(dev, "powering up");
+
+ // power up
+ if (a711->regulator) {
+ ret = regulator_enable(a711->regulator);
+ if (ret < 0) {
+ dev_err(dev, "can't enable power supply err=%d", ret);
+ return;
+ }
+ }
+
+ gpiod_set_value(a711->enable_gpio, 1);
+ gpiod_set_value(a711->reset_gpio, 1);
+ msleep(a711->reset_duration);
+ gpiod_set_value(a711->reset_gpio, 0);
+ a711->is_enabled = 1;
+}
+
+static struct class* a711_class;
+
+static void a711_work_handler(struct work_struct *work)
+{
+ struct a711_dev *a711 = container_of(work, struct a711_dev, work);
+ int last_request;
+
+ spin_lock(&a711->lock);
+ last_request = a711->last_request;
+ a711->last_request = 0;
+ spin_unlock(&a711->lock);
+
+ if (last_request == A711_REQ_RESET) {
+ a711_reset(a711);
+ } else if (last_request == A711_REQ_PWDN) {
+ a711_power_down(a711);
+ } else if (last_request == A711_REQ_PWUP) {
+ a711_power_up(a711);
+ }
+}
+
+static bool a711_has_wakeup(struct a711_dev* a711)
+{
+ bool got_wakeup;
+ spin_lock(&a711->lock);
+ got_wakeup = a711->got_wakeup;
+ spin_unlock(&a711->lock);
+ return got_wakeup;
+}
+
+static ssize_t a711_read(struct file *fp, char __user *buf, size_t len,
+ loff_t *off)
+{
+ struct a711_dev* a711 = fp->private_data;
+ int ret;
+ char tmp_buf[1] = {1};
+ int got_wakeup;
+ int non_blocking = fp->f_flags & O_NONBLOCK;
+
+ // first handle non-blocking path
+ if (non_blocking && !a711_has_wakeup(a711))
+ return -EWOULDBLOCK;
+
+ // wait for availability of wakeup
+ ret = wait_event_interruptible(a711->waitqueue,
+ a711_has_wakeup(a711));
+ if (ret)
+ return ret;
+
+ spin_lock(&a711->lock);
+
+ got_wakeup = a711->got_wakeup;
+ a711->got_wakeup = 0;
+
+ if (!got_wakeup) {
+ ret = -EIO;
+ } else {
+ if (copy_to_user(buf, tmp_buf, 1))
+ ret = -EFAULT;
+ else
+ ret = 1;
+ }
+
+ spin_unlock(&a711->lock);
+ return ret;
+}
+
+static ssize_t a711_write(struct file *fp, const char __user *buf,
+ size_t len, loff_t *off)
+{
+ struct a711_dev* a711 = fp->private_data;
+ int ret;
+ char tmp_buf[1];
+ int update = 0;
+
+ if (len == 0)
+ return 0;
+
+ ret = copy_from_user(tmp_buf, buf, 1);
+ if (ret)
+ return -EFAULT;
+
+ spin_lock(&a711->lock);
+
+ switch (tmp_buf[0]) {
+ case 'r':
+ a711->last_request = A711_REQ_RESET;
+ break;
+ case 'u':
+ a711->last_request = A711_REQ_PWUP;
+ break;
+ case 'd':
+ a711->last_request = A711_REQ_PWDN;
+ break;
+ default:
+ a711->last_request = A711_REQ_NONE;
+ break;
+ }
+
+ update = a711->last_request != 0;
+
+ spin_unlock(&a711->lock);
+
+ if (update)
+ schedule_work(&a711->work);
+
+ return 1;
+}
+
+static unsigned int a711_poll(struct file *fp, poll_table *wait)
+{
+ struct a711_dev* a711 = fp->private_data;
+ int ret = 0;
+
+ poll_wait(fp, &a711->waitqueue, wait);
+
+ spin_lock(&a711->lock);
+ if (a711->got_wakeup)
+ ret |= POLLIN | POLLRDNORM;
+ spin_unlock(&a711->lock);
+
+ return ret;
+}
+
+static long a711_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ struct a711_dev* a711 = fp->private_data;
+ unsigned long flags;
+ int ret = -ENOSYS;
+ void __user *parg = (void __user *)arg;
+ int powered;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+
+ spin_lock_irqsave(&a711->lock, flags);
+
+ switch (cmd) {
+ case A711_IOCTL_RESET:
+ a711->last_request = A711_REQ_RESET;
+ ret = 0;
+ break;
+ case A711_IOCTL_POWERUP:
+ a711->last_request = A711_REQ_PWUP;
+ ret = 0;
+ break;
+ case A711_IOCTL_POWERDN:
+ a711->last_request = A711_REQ_PWDN;
+ ret = 0;
+ break;
+ case A711_IOCTL_STATUS:
+ powered = a711->is_enabled || a711->last_request == A711_REQ_PWUP;
+ spin_unlock_irqrestore(&a711->lock, flags);
+
+ if (copy_to_user(parg, &powered, sizeof powered))
+ return -EFAULT;
+
+ return 0;
+ }
+
+ spin_unlock_irqrestore(&a711->lock, flags);
+
+ if (ret == 0)
+ schedule_work(&a711->work);
+
+ return ret;
+}
+
+static int a711_release(struct inode *ip, struct file *fp)
+{
+ struct a711_dev* a711 = fp->private_data;
+
+ spin_lock(&a711->lock);
+ a711->is_open = 0;
+ spin_unlock(&a711->lock);
+
+ return 0;
+}
+
+static int a711_open(struct inode *ip, struct file *fp)
+{
+ struct a711_dev* a711 = container_of(ip->i_cdev, struct a711_dev, cdev);
+
+ fp->private_data = a711;
+
+ spin_lock(&a711->lock);
+ if (a711->is_open) {
+ spin_unlock(&a711->lock);
+ return -EBUSY;
+ }
+
+ a711->is_open = 1;
+ spin_unlock(&a711->lock);
+
+ nonseekable_open(ip, fp);
+ return 0;
+}
+
+static const struct file_operations a711_fops = {
+ .owner = THIS_MODULE,
+ .read = a711_read,
+ .write = a711_write,
+ .poll = a711_poll,
+ .unlocked_ioctl = a711_ioctl,
+ .open = a711_open,
+ .release = a711_release,
+ .llseek = noop_llseek,
+};
+
+static irqreturn_t a711_wakeup_isr(int irq, void *dev_id)
+{
+ struct a711_dev *a711 = dev_id;
+ unsigned long flags;
+
+ spin_lock_irqsave(&a711->lock, flags);
+ a711->got_wakeup = 1;
+ spin_unlock_irqrestore(&a711->lock, flags);
+
+ wake_up_interruptible(&a711->waitqueue);
+
+ return IRQ_HANDLED;
+}
+
+static int a711_probe(struct platform_device *pdev)
+{
+ struct a711_dev *a711;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device *sdev;
+ const char* cdev_name = NULL;
+ int ret;
+
+ a711 = devm_kzalloc(dev, sizeof(*a711), GFP_KERNEL);
+ if (!a711)
+ return -ENOMEM;
+
+ a711->dev = dev;
+ platform_set_drvdata(pdev, a711);
+ init_waitqueue_head(&a711->waitqueue);
+ spin_lock_init(&a711->lock);
+ INIT_WORK(&a711->work, &a711_work_handler);
+
+ // get device name and reset time from device tree
+ ret = of_property_read_u32_index(np, "reset-duration-ms", 0,
+ &a711->reset_duration);
+ if (ret) {
+ a711->reset_duration = 10;
+ }
+
+ ret = of_property_read_string(np, "char-device-name", &cdev_name);
+ if (ret) {
+ dev_err(dev, "char-device-name is not configured");
+ return -EINVAL;
+ }
+
+ a711->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(a711->enable_gpio)) {
+ dev_err(dev, "can't get enable gpio err=%ld",
+ PTR_ERR(a711->enable_gpio));
+ return PTR_ERR(a711->enable_gpio);
+ }
+
+ a711->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(a711->reset_gpio)) {
+ dev_err(dev, "can't get reset gpio err=%ld",
+ PTR_ERR(a711->reset_gpio));
+ return PTR_ERR(a711->reset_gpio);
+ }
+
+ a711->wakeup_gpio = devm_gpiod_get_optional(dev, "wakeup", GPIOD_IN);
+ if (IS_ERR(a711->wakeup_gpio)) {
+ dev_err(dev, "can't get wakeup gpio err=%ld",
+ PTR_ERR(a711->wakeup_gpio));
+ return PTR_ERR(a711->wakeup_gpio);
+ }
+
+ a711->wakeup_irq = gpiod_to_irq(a711->wakeup_gpio);
+ if (a711->wakeup_irq > 0) {
+ ret = devm_request_irq(dev, a711->wakeup_irq,
+ a711_wakeup_isr,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ "a711-wakeup", a711);
+ if (ret) {
+ dev_err(dev, "error requesting wakeup-irq: %d", ret);
+ return ret;
+ }
+ }
+
+ a711->regulator = devm_regulator_get_optional(dev, "power");
+ if (IS_ERR(a711->regulator)) {
+ ret = PTR_ERR(a711->regulator);
+ if (ret != -ENODEV) {
+ dev_err(dev, "can't get power supply err=%d", ret);
+ return ret;
+ }
+
+ a711->regulator = NULL;
+ }
+
+ // create char device
+ ret = alloc_chrdev_region(&a711->major, 0, 1, "a711");
+ if (ret) {
+ dev_err(dev, "can't allocate chrdev region");
+ goto err_disable_regulator;
+ }
+
+ cdev_init(&a711->cdev, &a711_fops);
+ a711->cdev.owner = THIS_MODULE;
+ ret = cdev_add(&a711->cdev, a711->major, 1);
+ if (ret) {
+ dev_err(dev, "can't add cdev");
+ goto err_unreg_chrev_region;
+ }
+
+ sdev = device_create(a711_class, dev, a711->major, a711, cdev_name);
+ if (IS_ERR(sdev)) {
+ ret = PTR_ERR(sdev);
+ goto err_del_cdev;
+ }
+
+ gpiod_set_value(a711->enable_gpio, 0);
+ if (!a711->regulator)
+ gpiod_set_value(a711->reset_gpio, 1);
+
+ dev_info(dev, "initialized TBS A711 platform driver");
+
+ return 0;
+
+err_del_cdev:
+ cdev_del(&a711->cdev);
+err_unreg_chrev_region:
+ unregister_chrdev(a711->major, "a711");
+err_disable_regulator:
+ cancel_work_sync(&a711->work);
+ return ret;
+}
+
+static int a711_remove(struct platform_device *pdev)
+{
+ struct a711_dev *a711 = platform_get_drvdata(pdev);
+
+ a711_power_down(a711);
+
+ cancel_work_sync(&a711->work);
+
+ device_destroy(a711_class, a711->major);
+ cdev_del(&a711->cdev);
+ unregister_chrdev(a711->major, "a711");
+
+ if (a711->wakeup_irq > 0)
+ devm_free_irq(a711->dev, a711->wakeup_irq, a711);
+
+ return 0;
+}
+
+static const struct of_device_id a711_of_match[] = {
+ { .compatible = "custom,power-manager" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, a711_of_match);
+
+static struct platform_driver a711_platform_driver = {
+ .probe = a711_probe,
+ .remove = a711_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = a711_of_match,
+ },
+};
+
+static int __init a711_driver_init(void)
+{
+ int ret;
+
+ a711_class = class_create(THIS_MODULE, "a711");
+
+ ret = platform_driver_register(&a711_platform_driver);
+ if (ret)
+ class_destroy(a711_class);
+
+ return ret;
+}
+
+static void __exit a711_driver_exit(void)
+{
+ platform_driver_unregister(&a711_platform_driver);
+ class_destroy(a711_class);
+}
+
+module_init(a711_driver_init);
+module_exit(a711_driver_exit);
+
+MODULE_VERSION("1.0.0");
+MODULE_DESCRIPTION("TBS A711 Tablet Platform Driver");
+MODULE_AUTHOR("Ondrej Jirman <megous@megous.com>");
+MODULE_LICENSE("GPL v2");
--
2.20.1