blob: ffce4116c1b2540e0e97a4c67dd8a8b4e21a26a6 [file] [log] [blame]
/*
* GPIO edge detection driver for ASR eCall transaction
*
* Copyright 2021 ASR Microelectronics (Shanghai) Co., Ltd.
*
* This file is subject to the terms and conditions of the GNU General
* Public License. See the file "COPYING" in the main directory of this
* archive for more details.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/edge_wakeup_mmp.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <stddef.h>
#define ECALL_DEVICE_NUM 2
struct ecall_event_device {
char name[16];
struct device *dev;
int state;
int gpio;
int irq;
struct delayed_work work;
struct workqueue_struct *wq;
spinlock_t lock;
};
static struct class *ecall_event_class;
static struct ecall_event_device *guedevice[ECALL_DEVICE_NUM];
static const char* gpio_name[ECALL_DEVICE_NUM] = {
"gpio-auto-ecall",
"gpio-manual-ecall",
};
static void report_ecall_event(struct ecall_event_device *uedev, int state)
{
char name_buf[50];
char *env[3];
snprintf(name_buf, sizeof(name_buf), "%s", uedev->name);
env[0] = name_buf;
env[1] = state ? "GPIO_UP" : "GPIO_DOWN";
env[2] = NULL;
if (state)
kobject_uevent_env(&uedev->dev->kobj, KOBJ_ONLINE, env);
else
kobject_uevent_env(&uedev->dev->kobj, KOBJ_OFFLINE, env);
printk(KERN_INFO"%s: uevent from %s [%s] is sent\n",
__func__, env[0], env[1]);
}
static void ecall_event_work(struct work_struct *work)
{
struct ecall_event_device *uedev = container_of(to_delayed_work(work), struct ecall_event_device, work);
int state = gpio_get_value(uedev->gpio);
if (state != uedev->state) {
uedev->state = state;
report_ecall_event(uedev, state);
}
}
irqreturn_t ecall_event_handler(int irq, void *dev_id)
{
struct ecall_event_device *uedev = (struct ecall_event_device *)dev_id;
unsigned long flags = 0;
spin_lock_irqsave(&uedev->lock, flags);
if (work_pending(&uedev->work.work))
cancel_delayed_work(&uedev->work);
queue_delayed_work(uedev->wq, &uedev->work, HZ / 4);
spin_unlock_irqrestore(&uedev->lock, flags);
printk(KERN_INFO"%s: irq[%d]\n", __func__, irq);
return IRQ_HANDLED;
}
static ssize_t ecall_send_event(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
struct ecall_event_device *uedev = (struct ecall_event_device *)dev_get_drvdata(dev);
unsigned long state;
int ret = 0;
ret = kstrtoul(buf, 10, &state);
report_ecall_event(uedev, (int)state);
if (ret)
return ret;
else
return count;
}
static DEVICE_ATTR(send_event, 0220, NULL, ecall_send_event);
static struct device_attribute *ecall_event_attr[] = {
&dev_attr_send_event,
NULL
};
static int ecall_event_create_sys_device(struct device *dev)
{
int ret = 0;
struct device_attribute **attr = ecall_event_attr;
for (; *attr; ++attr) {
ret = device_create_file(dev, *attr);
if (ret)
break;
}
if (ret) {
for (--attr; attr >= ecall_event_attr; --attr)
device_remove_file(dev, *attr);
}
return 0;
}
static int ecall_event_remove_sys_device(struct device *dev)
{
struct device_attribute **attr = ecall_event_attr;
for (; *attr; ++attr)
device_remove_file(dev, *attr);
return 0;
}
static int init_device(struct platform_device *pdev, int dev_num)
{
int ret = -1;
unsigned long tmp;
struct ecall_event_device *uedevice = guedevice[dev_num];
snprintf(uedevice->name, sizeof(uedevice->name) - 1,
"ecall%d", dev_num);
spin_lock_init(&uedevice->lock);
uedevice->dev = device_create(ecall_event_class, NULL,
MKDEV(0, dev_num), NULL, uedevice->name);
if (IS_ERR(uedevice->dev)) {
printk(KERN_INFO"%s: create ecalldev failed!\n", __func__);
ret = PTR_ERR(uedevice->dev);
goto out;
}
dev_set_drvdata(uedevice->dev, uedevice);
of_property_read_u32(pdev->dev.of_node, gpio_name[dev_num], &uedevice->gpio);
if (uedevice->gpio >= 0) {
tmp = dev_num;
ret = request_mfp_edge_wakeup(uedevice->gpio,
NULL,
(void *)tmp, &pdev->dev);
if (ret) {
dev_err(uedevice->dev, "failed to request edge wakeup.\n");
goto edge_wakeup;
}
uedevice->state = gpio_get_value(uedevice->gpio);
}
ret = ecall_event_create_sys_device(uedevice->dev);
if (ret < 0) {
printk(KERN_INFO"%s: create sys device failed!\n", __func__);
goto destroy_device;
}
gpio_request(uedevice->gpio, uedevice->name);
gpio_direction_input(uedevice->gpio);
INIT_DELAYED_WORK(&uedevice->work, ecall_event_work);
uedevice->wq = create_workqueue(uedevice->name);
if (uedevice->wq == NULL) {
printk(KERN_INFO"%s: can't create work queue!\n", __func__);
ret = -ENOMEM;
goto free_gpio;
}
uedevice->irq = gpio_to_irq(uedevice->gpio);
ret = request_irq(uedevice->irq, ecall_event_handler,
IRQF_SHARED | IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, uedevice->name,
uedevice);
if (ret < 0) {
printk(KERN_INFO"%s: request irq failed!\n", __func__);
goto destroy_wq;
}
device_init_wakeup(uedevice->dev, 1);
ret = 0;
goto out;
destroy_wq:
destroy_workqueue(uedevice->wq);
free_gpio:
gpio_free(uedevice->gpio);
destroy_device:
device_destroy(ecall_event_class, MKDEV(0, dev_num));
edge_wakeup:
if (uedevice->gpio >= 0)
remove_mfp_edge_wakeup(uedevice->gpio);
out:
return ret;
}
static void deinit_device(int dev_num)
{
struct ecall_event_device *uedevice = guedevice[dev_num];
free_irq(uedevice->irq, uedevice);
if (uedevice->wq != NULL)
destroy_workqueue(uedevice->wq);
if (uedevice->gpio >= 0)
remove_mfp_edge_wakeup(uedevice->gpio);
gpio_free(uedevice->gpio);
ecall_event_remove_sys_device(uedevice->dev);
device_destroy(ecall_event_class, MKDEV(0, dev_num));
}
static int ecall_event_probe(struct platform_device *pdev)
{
int ret;
int dev_num;
if (!ecall_event_class)
ecall_event_class = class_create(THIS_MODULE, "ecall_event");
if (IS_ERR(ecall_event_class))
return PTR_ERR(ecall_event_class);
for (dev_num = 0; dev_num < ECALL_DEVICE_NUM; dev_num++) {
if (of_device_is_compatible(pdev->dev.of_node, "asr,ecall-event")) {
printk(KERN_INFO"%s: ecall-event\n", __func__);
} else {
printk(KERN_INFO"%s: unknown device\n", __func__);
goto deinit;
}
guedevice[dev_num] = kzalloc(sizeof(struct ecall_event_device), GFP_KERNEL);
ret = init_device(pdev, dev_num);
if (ret < 0)
goto deinit;
}
return 0;
deinit:
deinit_device(dev_num);
class_destroy(ecall_event_class);
return -1;
}
static int ecall_event_remove(struct platform_device *pdev)
{
int i;
for (i = 0; i < ECALL_DEVICE_NUM; i++)
deinit_device(i);
class_destroy(ecall_event_class);
for (i = 0; i < ECALL_DEVICE_NUM; i++) {
if (guedevice[i])
kfree(guedevice[i]);
}
return 0;
}
static const struct of_device_id ecall_of_match[] = {
{ .compatible = "asr,ecall-event",},
{},
};
MODULE_DEVICE_TABLE(of, ecall_of_match);
static struct platform_driver ecall_driver = {
.probe = ecall_event_probe,
.remove = ecall_event_remove,
.driver = {
.name = "ecall",
.owner = THIS_MODULE,
.of_match_table = ecall_of_match,
},
};
module_platform_driver(ecall_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shuo Dai");
MODULE_DESCRIPTION("ASR GPIO event for eCall");