blob: cc5e0ed79e118b9a503607b24c37a7957bbaa1bc [file] [edit]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* BBD PPS GPIO core file
*
* Copyright (C) 2024 Google LLC
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/gpio/consumer.h>
#include <linux/list.h>
#include <linux/property.h>
#include <linux/timer.h>
/* Info for each registered platform device */
struct bbd_pps_gpio_device_data {
int irq; /* IRQ used as PPS source */
struct gpio_desc *gpio_pin; /* GPIO port descriptors */
__u32 assert_sequence; /* PPS assert event seq # */
struct timespec64 g_assert_elapsed_tu; /* PPS elapsed rt assert seq # */
bool assert_falling_edge;
};
static ssize_t pps_assert_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct bbd_pps_gpio_device_data *bbd_pps_gpio = dev_get_drvdata(dev);
return sysfs_emit(buf, "%lld.%09ld#%u\n",
(long long)bbd_pps_gpio->g_assert_elapsed_tu.tv_sec,
bbd_pps_gpio->g_assert_elapsed_tu.tv_nsec,
bbd_pps_gpio->assert_sequence);
}
static DEVICE_ATTR_RO(pps_assert);
/* Handle the PPS pulse in the interrupt handler */
static irqreturn_t bbd_pps_gpio_handler(int irq, void *data)
{
struct bbd_pps_gpio_device_data *bbd_pps_gpio_device = data;
struct timespec64 ts64 = { .tv_sec = 0, .tv_nsec = 0 };
/*
* Calculate the monotonic clock from the timespec clock and stores the result
* in timespec64 format using ktime_get_boottime_ts64() for compatibility with
* the Android sensor system.
*/
ktime_get_boottime_ts64(&ts64);
bbd_pps_gpio_device->g_assert_elapsed_tu = ts64;
bbd_pps_gpio_device->assert_sequence++;
return IRQ_HANDLED;
}
static int bbd_pps_gpio_setup(struct device *dev)
{
struct bbd_pps_gpio_device_data *data = dev_get_drvdata(dev);
data->gpio_pin = devm_gpiod_get(dev, NULL, GPIOD_IN);
if (IS_ERR(data->gpio_pin))
return dev_err_probe(dev, PTR_ERR(data->gpio_pin),
"failed to request PPS GPIO\n");
data->assert_falling_edge =
device_property_read_bool(dev, "assert-falling-edge");
return 0;
}
/*
* Different from the mainline PPS GPIO driver, only two things
* 1. Probe the device-tree.
* 2. Register the GPIO interrupt.
*/
static int bbd_pps_gpio_probe(struct platform_device *pdev)
{
struct bbd_pps_gpio_device_data *data;
struct device *dev = &pdev->dev;
int ret;
unsigned long int_flags;
/* allocate space for device info */
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
dev_set_drvdata(dev, data);
/* GPIO setup */
ret = bbd_pps_gpio_setup(dev);
if (ret)
return ret;
/* IRQ setup */
ret = gpiod_to_irq(data->gpio_pin);
if (ret < 0) {
dev_err(dev, "failed to map GPIO to IRQ: %d\n", ret);
return -EINVAL;
}
data->irq = ret;
/* register IRQ interrupt handler */
int_flags = data->assert_falling_edge ? IRQF_TRIGGER_FALLING :
IRQF_TRIGGER_RISING;
ret = devm_request_irq(dev, data->irq, bbd_pps_gpio_handler, int_flags,
"BBD_GPIO", data);
if (ret) {
dev_err(dev, "failed to acquire IRQ %d\n", data->irq);
return -EINVAL;
}
if (device_create_file(dev, &dev_attr_pps_assert)) {
dev_err(dev, "failed to create device file pps_assert");
return -EINVAL;
}
return 0;
}
static int bbd_pps_gpio_remove(struct platform_device *pdev)
{
struct bbd_pps_gpio_device_data *data = platform_get_drvdata(pdev);
disable_irq_nosync(data->irq);
dev_dbg(&pdev->dev, "removed IRQ %d as PPS source\n", data->irq);
device_remove_file(&pdev->dev, &dev_attr_pps_assert);
return 0;
}
static int bbd_pps_gpio_suspend(struct device *dev)
{
struct bbd_pps_gpio_device_data *data = dev_get_drvdata(dev);
disable_irq_nosync(data->irq);
return 0;
}
static int bbd_pps_gpio_resume(struct device *dev)
{
struct bbd_pps_gpio_device_data *data = dev_get_drvdata(dev);
enable_irq(data->irq);
return 0;
}
static const struct dev_pm_ops bbd_pps_pm_ops = {
.suspend = bbd_pps_gpio_suspend,
.resume = bbd_pps_gpio_resume,
};
static const struct of_device_id pps_gpio_dt_ids[] = {
{
.compatible = "bbd-pps-gpio",
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pps_gpio_dt_ids);
static struct platform_driver bbd_pps_gpio_driver = {
.probe = bbd_pps_gpio_probe,
.remove = bbd_pps_gpio_remove,
.driver = {
.name = "bbd_pps_gpio",
.of_match_table = pps_gpio_dt_ids,
.pm = &bbd_pps_pm_ops,
},
};
/*
* Module init stuff
*/
static int __init bbd_pps_gpio_init(void)
{
return platform_driver_register(&bbd_pps_gpio_driver);
}
static void __exit bbd_pps_gpio_exit(void)
{
platform_driver_unregister(&bbd_pps_gpio_driver);
}
module_init(bbd_pps_gpio_init);
module_exit(bbd_pps_gpio_exit);
MODULE_AUTHOR("Cheng Change <[email protected]>");
MODULE_DESCRIPTION("Special PPS GPIO Handler");
MODULE_LICENSE("GPL");