blob: 1abeac0317845f5e6de58bcb1ab993cd1c43d18b [file] [log] [blame]
/*
* edge wakeup interface for MMP
*
* The GPIO Edge is the edge detect signals coming from the I/O pads.
* Although the name of this module is the GPIO Edge Unit, it can be
* used by other I/Os as it is not necessarily for use only by the
* GPIOs. It's normally used to wake up the system from low power mode.
*
* Copyright: (C) 2013 Marvell International Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/edge_wakeup_mmp.h>
#include "core.h"
/*
* struct edge_wakeup_desc - edge wakeup source descriptor, used for drivers
* whose pin is used to wakeup system.
* @list: list control of the descriptors
* @gpio: the gpio number of the wakeup source
* @dev: the device that gpio attaches to
* @data: optional, any kind private data passed to the handler
* @handler: optional, the handler for the certain wakeup source detected
* @state: used to save/restore pin state in LPM enter/exit
*/
struct edge_wakeup_desc {
struct list_head list;
int gpio;
struct device *dev;
void *data;
edge_handler handler;
const char *state_name;
};
struct edge_wakeup {
struct list_head list;
spinlock_t lock;
int num;
void __iomem *base;
int enabled;
};
static struct edge_wakeup *info;
static BLOCKING_NOTIFIER_HEAD(mfp_edge_wakeup_notifier);
int register_mfp_edge_wakup_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&mfp_edge_wakeup_notifier, nb);
}
EXPORT_SYMBOL_GPL(register_mfp_edge_wakup_notifier);
int unregister_mfp_edge_wakeup_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&mfp_edge_wakeup_notifier,
nb);
}
EXPORT_SYMBOL_GPL(unregister_mfp_edge_wakeup_notifier);
int mfp_edge_wakeup_notifier_call_chain(unsigned long msg, void *val)
{
int ret = blocking_notifier_call_chain(&mfp_edge_wakeup_notifier,
msg, val);
return notifier_to_errno(ret);
}
/*
* mmp_request/remove_edge_wakeup is called by common device driver.
*
* Drivers use it to set one or several pins as wakeup sources in deep low
* power modes.
*/
int request_mfp_edge_wakeup(int gpio, edge_handler handler, \
void *data, struct device *dev)
{
struct edge_wakeup_desc *desc, *e;
unsigned long flags;
if (dev == NULL) {
pr_err("error: edge wakeup: unknown device!\n");
return -EINVAL;
}
if (gpio < 0 || gpio > info->num) {
pr_err("error: edge wakeup: add invalid gpio num!\n");
return -EINVAL;
}
desc = kzalloc(sizeof(struct edge_wakeup_desc), GFP_KERNEL);
if (!desc)
return -ENOMEM;
desc->gpio = gpio;
desc->dev = dev;
desc->data = data;
desc->handler = handler;
spin_lock_irqsave(&info->lock, flags);
list_for_each_entry(e, &info->list, list) {
if (e->gpio == gpio) {
dev_err(dev, "Adding exist gpio%d to edge wakeup!\n", desc->gpio);
spin_unlock_irqrestore(&info->lock, flags);
kfree(desc);
return -EEXIST;
}
}
list_add(&desc->list, &info->list);
spin_unlock_irqrestore(&info->lock, flags);
/* Notify there is one GPIO added for wakeup */
mfp_edge_wakeup_notifier_call_chain(GPIO_ADD, &gpio);
return 0;
}
EXPORT_SYMBOL_GPL(request_mfp_edge_wakeup);
int edge_wakeup_mfp_status(unsigned long *edge_reg)
{
int i;
for (i = 0; i < (info->num / BITS_PER_LONG); i++) {
/*
* 64 and 32bits have different bytes length for "unsigned
* *long". For 64bits, need to combine regs value for edgew
* _rer[i]. Otherwise, the upper 32bits of edgew_rer[i]
* would be zero and test_and_clear_bit(e->gpio, edgew_rer)
* will find the wrong bit.
*/
#ifdef CONFIG_64BIT
unsigned long low_bits, high_bits;
low_bits = readl_relaxed(info->base + (2 * i) * 4);
high_bits = readl_relaxed(info->base + (2 * i + 1) * 4);
edge_reg[i] = low_bits | high_bits << 32;
#else
edge_reg[i] = readl_relaxed(info->base + i * 4);
#endif
}
return info->num / BITS_PER_LONG;
}
EXPORT_SYMBOL_GPL(edge_wakeup_mfp_status);
int remove_mfp_edge_wakeup(int gpio)
{
struct edge_wakeup_desc *e;
unsigned long flags;
if (gpio < 0 || gpio > info->num) {
pr_err("error: edge wakeup: remove invalid gpio num!\n");
return -EINVAL;
}
spin_lock_irqsave(&info->lock, flags);
list_for_each_entry(e, &info->list, list) {
if (e->gpio == gpio) {
list_del(&e->list);
spin_unlock_irqrestore(&info->lock, flags);
kfree(e);
/* Notify there is one GPIO removed */
mfp_edge_wakeup_notifier_call_chain(GPIO_REMOVE, &gpio);
return 0;
}
}
spin_unlock_irqrestore(&info->lock, flags);
pr_err("error: edge wakeup: del none exist gpio:%d!\n", gpio);
return -ENXIO;
}
EXPORT_SYMBOL_GPL(remove_mfp_edge_wakeup);
/*
* edge_wakeup_mfp_enable/disable is called by low power mode driver.
*
* edge_wakeup_mfp_enable Enable each gpio edge wakeup source in the list. The
* corresponing interrupt and wakeup port also should be set, which is done in
* low power mode driver.
*/
void edge_wakeup_mfp_enable(void)
{
struct edge_wakeup_desc *e;
struct pinctrl *pinctrl;
struct pinctrl_state *pin_sleep;
int ret;
spin_lock(&info->lock);
if (list_empty(&info->list)) {
spin_unlock(&info->lock);
return;
}
list_for_each_entry(e, &info->list, list) {
pinctrl = pinctrl_get(e->dev);
if (IS_ERR(pinctrl)) {
dev_err(e->dev, "Could not get pinctrl!\n");
continue;
}
/*
* If the pin has already set to its sleep state, (done by
* other pins in its group), then should not configure it
* again or its e->state_name will get the "sleep" value
* which is not expected.
*/
if (!strcmp(pinctrl->state->name, "sleep")) {
pinctrl_put(pinctrl);
continue;
}
pin_sleep = pinctrl_lookup_state(pinctrl, PINCTRL_STATE_SLEEP);
if (IS_ERR(pin_sleep)) {
dev_err(e->dev, "Could not get sleep pinstate!\n");
pinctrl_put(pinctrl);
continue;
}
e->state_name = pinctrl->state->name;
ret = pinctrl_select_state(pinctrl, pin_sleep);
if (ret)
dev_err(e->dev, "Could not set pins to sleep state!\n");
pinctrl_put(pinctrl);
}
info->enabled = 1;
spin_unlock(&info->lock);
}
EXPORT_SYMBOL_GPL(edge_wakeup_mfp_enable);
void edge_wakeup_mfp_disable(void)
{
struct edge_wakeup_desc *e;
struct pinctrl *pinctrl;
struct pinctrl_state *pin_def;
unsigned long *edgew_rer;
int i, ret;
/*
* check info when board boots up, at which point edge wakeup driver
* is not ready yet.
*/
if (info == NULL) {
pr_err("Edge wakeup driver is not initilized!\n");
return;
}
edgew_rer = kzalloc(info->num / 8 , GFP_KERNEL);
if (!edgew_rer) {
pr_err("Can't allocate enough memory for edge detection status!\n");
return;
}
spin_lock(&info->lock);
if (!info->enabled) {
spin_unlock(&info->lock);
kfree(edgew_rer);
return;
}
edge_wakeup_mfp_status(edgew_rer);
list_for_each_entry(e, &info->list, list) {
if (test_and_clear_bit(e->gpio, edgew_rer) && e->handler)
e->handler(e->gpio, e->data);
pinctrl = pinctrl_get(e->dev);
if (IS_ERR(pinctrl)) {
dev_err(e->dev, "Could not get pinctrl!\n");
continue;
}
/*
* If the pin has restored to its previous state before lpm
* (done by other pins in its group), then should not restore
* it again or it'll overwrite other pins' configurations in
* its group.
*/
if (strcmp(pinctrl->state->name, "sleep")) {
pinctrl_put(pinctrl);
continue;
}
pin_def = pinctrl_lookup_state(pinctrl, e->state_name);
if (IS_ERR(pin_def)) {
dev_err(e->dev, "Could not get default pinstate!\n");
pinctrl_put(pinctrl);
continue;
}
ret = pinctrl_select_state(pinctrl, pin_def);
if (ret)
dev_err(e->dev, "Could not set pins to default state!\n");
pinctrl_put(pinctrl);
}
info->enabled = 0;
spin_unlock(&info->lock);
i = find_first_bit(edgew_rer, info->num);
while (i < info->num) {
pr_err("error: edge wakeup: unexpected detect gpio%d wakeup!\n", i);
i = find_next_bit(edgew_rer, info->num, i + 1);
}
kfree(edgew_rer);
return;
}
EXPORT_SYMBOL_GPL(edge_wakeup_mfp_disable);
static int edge_wakeup_mfp_probe(struct platform_device *pdev)
{
void __iomem *base;
int size;
if (IS_ENABLED(CONFIG_OF)) {
struct device_node *np = pdev->dev.of_node;
const __be32 *prop = of_get_property(np, "reg", NULL);
base = of_iomap(np, 0);
if (!base) {
dev_err(&pdev->dev, "Fail to map base address\n");
return -EINVAL;
}
size = be32_to_cpup(prop + 1);
} else {
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "no memory resource defined\n");
return -ENODEV;
}
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
size = resource_size(res);
}
info = devm_kzalloc(&pdev->dev, sizeof(struct edge_wakeup), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->base = base;
/* size represents bytes num, and each bit represents a mfp */
info->num = size * 8;
info->enabled = 0;
spin_lock_init(&info->lock);
INIT_LIST_HEAD(&info->list);
platform_set_drvdata(pdev, info);
return 0;
}
#ifdef CONFIG_OF
static struct of_device_id edge_wakeup_mfp_dt_ids[] = {
{ .compatible = "mrvl,mmp-edge-wakeup", },
{}
};
MODULE_DEVICE_TABLE(of, edge_wakeup_mfp_dt_ids);
#endif
static struct platform_driver edge_wakeup_driver = {
.probe = edge_wakeup_mfp_probe,
.driver = {
.name = "mmp-edge-wakeup",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(edge_wakeup_mfp_dt_ids),
},
};
static int __init edge_wakeup_driver_init(void)
{
return platform_driver_register(&edge_wakeup_driver);
}
subsys_initcall(edge_wakeup_driver_init);
MODULE_AUTHOR("Fangsuo Wu <fswu@marvell.com>");
MODULE_DESCRIPTION("MMP Edge Wakeup Driver");
MODULE_LICENSE("GPL v2");