18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * pps-gpio.c -- PPS client driver using GPIO 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2010 Ricardo Martins <rasm@fe.up.pt> 68c2ecf20Sopenharmony_ci * Copyright (C) 2011 James Nuss <jamesnuss@nanometrics.ca> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#define PPS_GPIO_NAME "pps-gpio" 108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) PPS_GPIO_NAME ": " fmt 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/pps_kernel.h> 198c2ecf20Sopenharmony_ci#include <linux/pps-gpio.h> 208c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 218c2ecf20Sopenharmony_ci#include <linux/list.h> 228c2ecf20Sopenharmony_ci#include <linux/of_device.h> 238c2ecf20Sopenharmony_ci#include <linux/of_gpio.h> 248c2ecf20Sopenharmony_ci#include <linux/timer.h> 258c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/* Info for each registered platform device */ 288c2ecf20Sopenharmony_cistruct pps_gpio_device_data { 298c2ecf20Sopenharmony_ci int irq; /* IRQ used as PPS source */ 308c2ecf20Sopenharmony_ci struct pps_device *pps; /* PPS source device */ 318c2ecf20Sopenharmony_ci struct pps_source_info info; /* PPS source information */ 328c2ecf20Sopenharmony_ci struct gpio_desc *gpio_pin; /* GPIO port descriptors */ 338c2ecf20Sopenharmony_ci struct gpio_desc *echo_pin; 348c2ecf20Sopenharmony_ci struct timer_list echo_timer; /* timer to reset echo active state */ 358c2ecf20Sopenharmony_ci bool assert_falling_edge; 368c2ecf20Sopenharmony_ci bool capture_clear; 378c2ecf20Sopenharmony_ci unsigned int echo_active_ms; /* PPS echo active duration */ 388c2ecf20Sopenharmony_ci unsigned long echo_timeout; /* timer timeout value in jiffies */ 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci/* 428c2ecf20Sopenharmony_ci * Report the PPS event 438c2ecf20Sopenharmony_ci */ 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic irqreturn_t pps_gpio_irq_handler(int irq, void *data) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci const struct pps_gpio_device_data *info; 488c2ecf20Sopenharmony_ci struct pps_event_time ts; 498c2ecf20Sopenharmony_ci int rising_edge; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci /* Get the time stamp first */ 528c2ecf20Sopenharmony_ci pps_get_ts(&ts); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci info = data; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci rising_edge = gpiod_get_value(info->gpio_pin); 578c2ecf20Sopenharmony_ci if ((rising_edge && !info->assert_falling_edge) || 588c2ecf20Sopenharmony_ci (!rising_edge && info->assert_falling_edge)) 598c2ecf20Sopenharmony_ci pps_event(info->pps, &ts, PPS_CAPTUREASSERT, data); 608c2ecf20Sopenharmony_ci else if (info->capture_clear && 618c2ecf20Sopenharmony_ci ((rising_edge && info->assert_falling_edge) || 628c2ecf20Sopenharmony_ci (!rising_edge && !info->assert_falling_edge))) 638c2ecf20Sopenharmony_ci pps_event(info->pps, &ts, PPS_CAPTURECLEAR, data); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci return IRQ_HANDLED; 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci/* This function will only be called when an ECHO GPIO is defined */ 698c2ecf20Sopenharmony_cistatic void pps_gpio_echo(struct pps_device *pps, int event, void *data) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci /* add_timer() needs to write into info->echo_timer */ 728c2ecf20Sopenharmony_ci struct pps_gpio_device_data *info = data; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci switch (event) { 758c2ecf20Sopenharmony_ci case PPS_CAPTUREASSERT: 768c2ecf20Sopenharmony_ci if (pps->params.mode & PPS_ECHOASSERT) 778c2ecf20Sopenharmony_ci gpiod_set_value(info->echo_pin, 1); 788c2ecf20Sopenharmony_ci break; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci case PPS_CAPTURECLEAR: 818c2ecf20Sopenharmony_ci if (pps->params.mode & PPS_ECHOCLEAR) 828c2ecf20Sopenharmony_ci gpiod_set_value(info->echo_pin, 1); 838c2ecf20Sopenharmony_ci break; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci /* fire the timer */ 878c2ecf20Sopenharmony_ci if (info->pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) { 888c2ecf20Sopenharmony_ci info->echo_timer.expires = jiffies + info->echo_timeout; 898c2ecf20Sopenharmony_ci add_timer(&info->echo_timer); 908c2ecf20Sopenharmony_ci } 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci/* Timer callback to reset the echo pin to the inactive state */ 948c2ecf20Sopenharmony_cistatic void pps_gpio_echo_timer_callback(struct timer_list *t) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci const struct pps_gpio_device_data *info; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci info = from_timer(info, t, echo_timer); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci gpiod_set_value(info->echo_pin, 0); 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic int pps_gpio_setup(struct platform_device *pdev) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci struct pps_gpio_device_data *data = platform_get_drvdata(pdev); 1068c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 1078c2ecf20Sopenharmony_ci int ret; 1088c2ecf20Sopenharmony_ci u32 value; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci data->gpio_pin = devm_gpiod_get(&pdev->dev, 1118c2ecf20Sopenharmony_ci NULL, /* request "gpios" */ 1128c2ecf20Sopenharmony_ci GPIOD_IN); 1138c2ecf20Sopenharmony_ci if (IS_ERR(data->gpio_pin)) { 1148c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 1158c2ecf20Sopenharmony_ci "failed to request PPS GPIO\n"); 1168c2ecf20Sopenharmony_ci return PTR_ERR(data->gpio_pin); 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci data->echo_pin = devm_gpiod_get_optional(&pdev->dev, 1208c2ecf20Sopenharmony_ci "echo", 1218c2ecf20Sopenharmony_ci GPIOD_OUT_LOW); 1228c2ecf20Sopenharmony_ci if (data->echo_pin) { 1238c2ecf20Sopenharmony_ci if (IS_ERR(data->echo_pin)) { 1248c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to request ECHO GPIO\n"); 1258c2ecf20Sopenharmony_ci return PTR_ERR(data->echo_pin); 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci ret = of_property_read_u32(np, 1298c2ecf20Sopenharmony_ci "echo-active-ms", 1308c2ecf20Sopenharmony_ci &value); 1318c2ecf20Sopenharmony_ci if (ret) { 1328c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 1338c2ecf20Sopenharmony_ci "failed to get echo-active-ms from OF\n"); 1348c2ecf20Sopenharmony_ci return ret; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci data->echo_active_ms = value; 1378c2ecf20Sopenharmony_ci /* sanity check on echo_active_ms */ 1388c2ecf20Sopenharmony_ci if (!data->echo_active_ms || data->echo_active_ms > 999) { 1398c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 1408c2ecf20Sopenharmony_ci "echo-active-ms: %u - bad value from OF\n", 1418c2ecf20Sopenharmony_ci data->echo_active_ms); 1428c2ecf20Sopenharmony_ci return -EINVAL; 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "assert-falling-edge")) 1478c2ecf20Sopenharmony_ci data->assert_falling_edge = true; 1488c2ecf20Sopenharmony_ci return 0; 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic unsigned long 1528c2ecf20Sopenharmony_ciget_irqf_trigger_flags(const struct pps_gpio_device_data *data) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci unsigned long flags = data->assert_falling_edge ? 1558c2ecf20Sopenharmony_ci IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci if (data->capture_clear) { 1588c2ecf20Sopenharmony_ci flags |= ((flags & IRQF_TRIGGER_RISING) ? 1598c2ecf20Sopenharmony_ci IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING); 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci return flags; 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic int pps_gpio_probe(struct platform_device *pdev) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci struct pps_gpio_device_data *data; 1688c2ecf20Sopenharmony_ci int ret; 1698c2ecf20Sopenharmony_ci int pps_default_params; 1708c2ecf20Sopenharmony_ci const struct pps_gpio_platform_data *pdata = pdev->dev.platform_data; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci /* allocate space for device info */ 1738c2ecf20Sopenharmony_ci data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 1748c2ecf20Sopenharmony_ci if (!data) 1758c2ecf20Sopenharmony_ci return -ENOMEM; 1768c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, data); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci /* GPIO setup */ 1798c2ecf20Sopenharmony_ci if (pdata) { 1808c2ecf20Sopenharmony_ci data->gpio_pin = pdata->gpio_pin; 1818c2ecf20Sopenharmony_ci data->echo_pin = pdata->echo_pin; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci data->assert_falling_edge = pdata->assert_falling_edge; 1848c2ecf20Sopenharmony_ci data->capture_clear = pdata->capture_clear; 1858c2ecf20Sopenharmony_ci data->echo_active_ms = pdata->echo_active_ms; 1868c2ecf20Sopenharmony_ci } else { 1878c2ecf20Sopenharmony_ci ret = pps_gpio_setup(pdev); 1888c2ecf20Sopenharmony_ci if (ret) 1898c2ecf20Sopenharmony_ci return -EINVAL; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci /* IRQ setup */ 1938c2ecf20Sopenharmony_ci ret = gpiod_to_irq(data->gpio_pin); 1948c2ecf20Sopenharmony_ci if (ret < 0) { 1958c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to map GPIO to IRQ: %d\n", ret); 1968c2ecf20Sopenharmony_ci return -EINVAL; 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci data->irq = ret; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci /* initialize PPS specific parts of the bookkeeping data structure. */ 2018c2ecf20Sopenharmony_ci data->info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | 2028c2ecf20Sopenharmony_ci PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC; 2038c2ecf20Sopenharmony_ci if (data->capture_clear) 2048c2ecf20Sopenharmony_ci data->info.mode |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR | 2058c2ecf20Sopenharmony_ci PPS_ECHOCLEAR; 2068c2ecf20Sopenharmony_ci data->info.owner = THIS_MODULE; 2078c2ecf20Sopenharmony_ci snprintf(data->info.name, PPS_MAX_NAME_LEN - 1, "%s.%d", 2088c2ecf20Sopenharmony_ci pdev->name, pdev->id); 2098c2ecf20Sopenharmony_ci if (data->echo_pin) { 2108c2ecf20Sopenharmony_ci data->info.echo = pps_gpio_echo; 2118c2ecf20Sopenharmony_ci data->echo_timeout = msecs_to_jiffies(data->echo_active_ms); 2128c2ecf20Sopenharmony_ci timer_setup(&data->echo_timer, pps_gpio_echo_timer_callback, 0); 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci /* register PPS source */ 2168c2ecf20Sopenharmony_ci pps_default_params = PPS_CAPTUREASSERT | PPS_OFFSETASSERT; 2178c2ecf20Sopenharmony_ci if (data->capture_clear) 2188c2ecf20Sopenharmony_ci pps_default_params |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR; 2198c2ecf20Sopenharmony_ci data->pps = pps_register_source(&data->info, pps_default_params); 2208c2ecf20Sopenharmony_ci if (IS_ERR(data->pps)) { 2218c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to register IRQ %d as PPS source\n", 2228c2ecf20Sopenharmony_ci data->irq); 2238c2ecf20Sopenharmony_ci return PTR_ERR(data->pps); 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci /* register IRQ interrupt handler */ 2278c2ecf20Sopenharmony_ci ret = devm_request_irq(&pdev->dev, data->irq, pps_gpio_irq_handler, 2288c2ecf20Sopenharmony_ci get_irqf_trigger_flags(data), data->info.name, data); 2298c2ecf20Sopenharmony_ci if (ret) { 2308c2ecf20Sopenharmony_ci pps_unregister_source(data->pps); 2318c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to acquire IRQ %d\n", data->irq); 2328c2ecf20Sopenharmony_ci return -EINVAL; 2338c2ecf20Sopenharmony_ci } 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci dev_info(data->pps->dev, "Registered IRQ %d as PPS source\n", 2368c2ecf20Sopenharmony_ci data->irq); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci return 0; 2398c2ecf20Sopenharmony_ci} 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_cistatic int pps_gpio_remove(struct platform_device *pdev) 2428c2ecf20Sopenharmony_ci{ 2438c2ecf20Sopenharmony_ci struct pps_gpio_device_data *data = platform_get_drvdata(pdev); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci pps_unregister_source(data->pps); 2468c2ecf20Sopenharmony_ci if (data->echo_pin) { 2478c2ecf20Sopenharmony_ci del_timer_sync(&data->echo_timer); 2488c2ecf20Sopenharmony_ci /* reset echo pin in any case */ 2498c2ecf20Sopenharmony_ci gpiod_set_value(data->echo_pin, 0); 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "removed IRQ %d as PPS source\n", data->irq); 2528c2ecf20Sopenharmony_ci return 0; 2538c2ecf20Sopenharmony_ci} 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_cistatic const struct of_device_id pps_gpio_dt_ids[] = { 2568c2ecf20Sopenharmony_ci { .compatible = "pps-gpio", }, 2578c2ecf20Sopenharmony_ci { /* sentinel */ } 2588c2ecf20Sopenharmony_ci}; 2598c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, pps_gpio_dt_ids); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cistatic struct platform_driver pps_gpio_driver = { 2628c2ecf20Sopenharmony_ci .probe = pps_gpio_probe, 2638c2ecf20Sopenharmony_ci .remove = pps_gpio_remove, 2648c2ecf20Sopenharmony_ci .driver = { 2658c2ecf20Sopenharmony_ci .name = PPS_GPIO_NAME, 2668c2ecf20Sopenharmony_ci .of_match_table = pps_gpio_dt_ids, 2678c2ecf20Sopenharmony_ci }, 2688c2ecf20Sopenharmony_ci}; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cimodule_platform_driver(pps_gpio_driver); 2718c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ricardo Martins <rasm@fe.up.pt>"); 2728c2ecf20Sopenharmony_ciMODULE_AUTHOR("James Nuss <jamesnuss@nanometrics.ca>"); 2738c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Use GPIO pin as PPS source"); 2748c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2758c2ecf20Sopenharmony_ciMODULE_VERSION("1.2.0"); 276