162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * pps-gpio.c -- PPS client driver using GPIO 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2010 Ricardo Martins <rasm@fe.up.pt> 662306a36Sopenharmony_ci * Copyright (C) 2011 James Nuss <jamesnuss@nanometrics.ca> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define PPS_GPIO_NAME "pps-gpio" 1062306a36Sopenharmony_ci#define pr_fmt(fmt) PPS_GPIO_NAME ": " fmt 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/interrupt.h> 1562306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <linux/pps_kernel.h> 2062306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 2162306a36Sopenharmony_ci#include <linux/list.h> 2262306a36Sopenharmony_ci#include <linux/property.h> 2362306a36Sopenharmony_ci#include <linux/timer.h> 2462306a36Sopenharmony_ci#include <linux/jiffies.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* Info for each registered platform device */ 2762306a36Sopenharmony_cistruct pps_gpio_device_data { 2862306a36Sopenharmony_ci int irq; /* IRQ used as PPS source */ 2962306a36Sopenharmony_ci struct pps_device *pps; /* PPS source device */ 3062306a36Sopenharmony_ci struct pps_source_info info; /* PPS source information */ 3162306a36Sopenharmony_ci struct gpio_desc *gpio_pin; /* GPIO port descriptors */ 3262306a36Sopenharmony_ci struct gpio_desc *echo_pin; 3362306a36Sopenharmony_ci struct timer_list echo_timer; /* timer to reset echo active state */ 3462306a36Sopenharmony_ci bool assert_falling_edge; 3562306a36Sopenharmony_ci bool capture_clear; 3662306a36Sopenharmony_ci unsigned int echo_active_ms; /* PPS echo active duration */ 3762306a36Sopenharmony_ci unsigned long echo_timeout; /* timer timeout value in jiffies */ 3862306a36Sopenharmony_ci}; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/* 4162306a36Sopenharmony_ci * Report the PPS event 4262306a36Sopenharmony_ci */ 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic irqreturn_t pps_gpio_irq_handler(int irq, void *data) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci const struct pps_gpio_device_data *info; 4762306a36Sopenharmony_ci struct pps_event_time ts; 4862306a36Sopenharmony_ci int rising_edge; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci /* Get the time stamp first */ 5162306a36Sopenharmony_ci pps_get_ts(&ts); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci info = data; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci rising_edge = gpiod_get_value(info->gpio_pin); 5662306a36Sopenharmony_ci if ((rising_edge && !info->assert_falling_edge) || 5762306a36Sopenharmony_ci (!rising_edge && info->assert_falling_edge)) 5862306a36Sopenharmony_ci pps_event(info->pps, &ts, PPS_CAPTUREASSERT, data); 5962306a36Sopenharmony_ci else if (info->capture_clear && 6062306a36Sopenharmony_ci ((rising_edge && info->assert_falling_edge) || 6162306a36Sopenharmony_ci (!rising_edge && !info->assert_falling_edge))) 6262306a36Sopenharmony_ci pps_event(info->pps, &ts, PPS_CAPTURECLEAR, data); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return IRQ_HANDLED; 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci/* This function will only be called when an ECHO GPIO is defined */ 6862306a36Sopenharmony_cistatic void pps_gpio_echo(struct pps_device *pps, int event, void *data) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci /* add_timer() needs to write into info->echo_timer */ 7162306a36Sopenharmony_ci struct pps_gpio_device_data *info = data; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci switch (event) { 7462306a36Sopenharmony_ci case PPS_CAPTUREASSERT: 7562306a36Sopenharmony_ci if (pps->params.mode & PPS_ECHOASSERT) 7662306a36Sopenharmony_ci gpiod_set_value(info->echo_pin, 1); 7762306a36Sopenharmony_ci break; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci case PPS_CAPTURECLEAR: 8062306a36Sopenharmony_ci if (pps->params.mode & PPS_ECHOCLEAR) 8162306a36Sopenharmony_ci gpiod_set_value(info->echo_pin, 1); 8262306a36Sopenharmony_ci break; 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* fire the timer */ 8662306a36Sopenharmony_ci if (info->pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) { 8762306a36Sopenharmony_ci info->echo_timer.expires = jiffies + info->echo_timeout; 8862306a36Sopenharmony_ci add_timer(&info->echo_timer); 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci/* Timer callback to reset the echo pin to the inactive state */ 9362306a36Sopenharmony_cistatic void pps_gpio_echo_timer_callback(struct timer_list *t) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci const struct pps_gpio_device_data *info; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci info = from_timer(info, t, echo_timer); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci gpiod_set_value(info->echo_pin, 0); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic int pps_gpio_setup(struct device *dev) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct pps_gpio_device_data *data = dev_get_drvdata(dev); 10562306a36Sopenharmony_ci int ret; 10662306a36Sopenharmony_ci u32 value; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci data->gpio_pin = devm_gpiod_get(dev, NULL, GPIOD_IN); 10962306a36Sopenharmony_ci if (IS_ERR(data->gpio_pin)) 11062306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(data->gpio_pin), 11162306a36Sopenharmony_ci "failed to request PPS GPIO\n"); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci data->assert_falling_edge = 11462306a36Sopenharmony_ci device_property_read_bool(dev, "assert-falling-edge"); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci data->echo_pin = devm_gpiod_get_optional(dev, "echo", GPIOD_OUT_LOW); 11762306a36Sopenharmony_ci if (IS_ERR(data->echo_pin)) 11862306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(data->echo_pin), 11962306a36Sopenharmony_ci "failed to request ECHO GPIO\n"); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci if (!data->echo_pin) 12262306a36Sopenharmony_ci return 0; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci ret = device_property_read_u32(dev, "echo-active-ms", &value); 12562306a36Sopenharmony_ci if (ret) { 12662306a36Sopenharmony_ci dev_err(dev, "failed to get echo-active-ms from FW\n"); 12762306a36Sopenharmony_ci return ret; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci /* sanity check on echo_active_ms */ 13162306a36Sopenharmony_ci if (!value || value > 999) { 13262306a36Sopenharmony_ci dev_err(dev, "echo-active-ms: %u - bad value from FW\n", value); 13362306a36Sopenharmony_ci return -EINVAL; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci data->echo_active_ms = value; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return 0; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic unsigned long 14262306a36Sopenharmony_ciget_irqf_trigger_flags(const struct pps_gpio_device_data *data) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci unsigned long flags = data->assert_falling_edge ? 14562306a36Sopenharmony_ci IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci if (data->capture_clear) { 14862306a36Sopenharmony_ci flags |= ((flags & IRQF_TRIGGER_RISING) ? 14962306a36Sopenharmony_ci IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING); 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci return flags; 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic int pps_gpio_probe(struct platform_device *pdev) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci struct pps_gpio_device_data *data; 15862306a36Sopenharmony_ci struct device *dev = &pdev->dev; 15962306a36Sopenharmony_ci int ret; 16062306a36Sopenharmony_ci int pps_default_params; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* allocate space for device info */ 16362306a36Sopenharmony_ci data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 16462306a36Sopenharmony_ci if (!data) 16562306a36Sopenharmony_ci return -ENOMEM; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci dev_set_drvdata(dev, data); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci /* GPIO setup */ 17062306a36Sopenharmony_ci ret = pps_gpio_setup(dev); 17162306a36Sopenharmony_ci if (ret) 17262306a36Sopenharmony_ci return ret; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* IRQ setup */ 17562306a36Sopenharmony_ci ret = gpiod_to_irq(data->gpio_pin); 17662306a36Sopenharmony_ci if (ret < 0) { 17762306a36Sopenharmony_ci dev_err(dev, "failed to map GPIO to IRQ: %d\n", ret); 17862306a36Sopenharmony_ci return -EINVAL; 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci data->irq = ret; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* initialize PPS specific parts of the bookkeeping data structure. */ 18362306a36Sopenharmony_ci data->info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | 18462306a36Sopenharmony_ci PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC; 18562306a36Sopenharmony_ci if (data->capture_clear) 18662306a36Sopenharmony_ci data->info.mode |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR | 18762306a36Sopenharmony_ci PPS_ECHOCLEAR; 18862306a36Sopenharmony_ci data->info.owner = THIS_MODULE; 18962306a36Sopenharmony_ci snprintf(data->info.name, PPS_MAX_NAME_LEN - 1, "%s.%d", 19062306a36Sopenharmony_ci pdev->name, pdev->id); 19162306a36Sopenharmony_ci if (data->echo_pin) { 19262306a36Sopenharmony_ci data->info.echo = pps_gpio_echo; 19362306a36Sopenharmony_ci data->echo_timeout = msecs_to_jiffies(data->echo_active_ms); 19462306a36Sopenharmony_ci timer_setup(&data->echo_timer, pps_gpio_echo_timer_callback, 0); 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci /* register PPS source */ 19862306a36Sopenharmony_ci pps_default_params = PPS_CAPTUREASSERT | PPS_OFFSETASSERT; 19962306a36Sopenharmony_ci if (data->capture_clear) 20062306a36Sopenharmony_ci pps_default_params |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR; 20162306a36Sopenharmony_ci data->pps = pps_register_source(&data->info, pps_default_params); 20262306a36Sopenharmony_ci if (IS_ERR(data->pps)) { 20362306a36Sopenharmony_ci dev_err(dev, "failed to register IRQ %d as PPS source\n", 20462306a36Sopenharmony_ci data->irq); 20562306a36Sopenharmony_ci return PTR_ERR(data->pps); 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci /* register IRQ interrupt handler */ 20962306a36Sopenharmony_ci ret = devm_request_irq(dev, data->irq, pps_gpio_irq_handler, 21062306a36Sopenharmony_ci get_irqf_trigger_flags(data), data->info.name, data); 21162306a36Sopenharmony_ci if (ret) { 21262306a36Sopenharmony_ci pps_unregister_source(data->pps); 21362306a36Sopenharmony_ci dev_err(dev, "failed to acquire IRQ %d\n", data->irq); 21462306a36Sopenharmony_ci return -EINVAL; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci dev_info(data->pps->dev, "Registered IRQ %d as PPS source\n", 21862306a36Sopenharmony_ci data->irq); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci return 0; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic int pps_gpio_remove(struct platform_device *pdev) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci struct pps_gpio_device_data *data = platform_get_drvdata(pdev); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci pps_unregister_source(data->pps); 22862306a36Sopenharmony_ci del_timer_sync(&data->echo_timer); 22962306a36Sopenharmony_ci /* reset echo pin in any case */ 23062306a36Sopenharmony_ci gpiod_set_value(data->echo_pin, 0); 23162306a36Sopenharmony_ci dev_info(&pdev->dev, "removed IRQ %d as PPS source\n", data->irq); 23262306a36Sopenharmony_ci return 0; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistatic const struct of_device_id pps_gpio_dt_ids[] = { 23662306a36Sopenharmony_ci { .compatible = "pps-gpio", }, 23762306a36Sopenharmony_ci { /* sentinel */ } 23862306a36Sopenharmony_ci}; 23962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, pps_gpio_dt_ids); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic struct platform_driver pps_gpio_driver = { 24262306a36Sopenharmony_ci .probe = pps_gpio_probe, 24362306a36Sopenharmony_ci .remove = pps_gpio_remove, 24462306a36Sopenharmony_ci .driver = { 24562306a36Sopenharmony_ci .name = PPS_GPIO_NAME, 24662306a36Sopenharmony_ci .of_match_table = pps_gpio_dt_ids, 24762306a36Sopenharmony_ci }, 24862306a36Sopenharmony_ci}; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cimodule_platform_driver(pps_gpio_driver); 25162306a36Sopenharmony_ciMODULE_AUTHOR("Ricardo Martins <rasm@fe.up.pt>"); 25262306a36Sopenharmony_ciMODULE_AUTHOR("James Nuss <jamesnuss@nanometrics.ca>"); 25362306a36Sopenharmony_ciMODULE_DESCRIPTION("Use GPIO pin as PPS source"); 25462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 25562306a36Sopenharmony_ciMODULE_VERSION("1.2.0"); 256