Commit | Line | Data |
---|---|---|
9a75a7cd DP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (c) 2021-2022 NVIDIA Corporation | |
4 | * | |
5 | * Author: Dipen Patel <dipenp@nvidia.com> | |
6 | */ | |
7 | ||
9a75a7cd DP |
8 | #include <linux/err.h> |
9 | #include <linux/module.h> | |
10 | #include <linux/moduleparam.h> | |
11 | #include <linux/interrupt.h> | |
12 | #include <linux/gpio.h> | |
13 | #include <linux/timer.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/workqueue.h> | |
16 | #include <linux/hte.h> | |
17 | ||
18 | /* | |
19 | * This sample HTE GPIO test driver demonstrates HTE API usage by enabling | |
20 | * hardware timestamp on gpio_in and specified LIC IRQ lines. | |
21 | * | |
22 | * Note: gpio_out and gpio_in need to be shorted externally in order for this | |
23 | * test driver to work for the GPIO monitoring. The test driver has been | |
24 | * tested on Jetson AGX Xavier platform by shorting pin 32 and 16 on 40 pin | |
25 | * header. | |
26 | * | |
27 | * Device tree snippet to activate this driver: | |
28 | * tegra_hte_test { | |
29 | * compatible = "nvidia,tegra194-hte-test"; | |
30 | * in-gpio = <&gpio_aon TEGRA194_AON_GPIO(BB, 1)>; | |
31 | * out-gpio = <&gpio_aon TEGRA194_AON_GPIO(BB, 0)>; | |
32 | * timestamps = <&tegra_hte_aon TEGRA194_AON_GPIO(BB, 1)>, | |
33 | * <&tegra_hte_lic 0x19>; | |
34 | * timestamp-names = "hte-gpio", "hte-i2c-irq"; | |
35 | * status = "okay"; | |
36 | * }; | |
37 | * | |
38 | * How to run test driver: | |
39 | * - Load test driver. | |
40 | * - For the GPIO, at regular interval gpio_out pin toggles triggering | |
41 | * HTE for rising edge on gpio_in pin. | |
42 | * | |
43 | * - For the LIC IRQ line, it uses 0x19 interrupt which is i2c controller 1. | |
44 | * - Run i2cdetect -y 1 1>/dev/null, this command will generate i2c bus | |
45 | * transactions which creates timestamp data. | |
46 | * - It prints below message for both the lines. | |
47 | * HW timestamp(<line id>:<ts seq number>): <timestamp>, edge: <edge>. | |
48 | * - Unloading the driver disables and deallocate the HTE. | |
49 | */ | |
50 | ||
51 | static struct tegra_hte_test { | |
52 | int gpio_in_irq; | |
53 | struct device *pdev; | |
54 | struct gpio_desc *gpio_in; | |
55 | struct gpio_desc *gpio_out; | |
56 | struct hte_ts_desc *desc; | |
57 | struct timer_list timer; | |
58 | struct kobject *kobj; | |
59 | } hte; | |
60 | ||
61 | static enum hte_return process_hw_ts(struct hte_ts_data *ts, void *p) | |
62 | { | |
63 | char *edge; | |
64 | struct hte_ts_desc *desc = p; | |
65 | ||
66 | if (!ts || !p) | |
67 | return HTE_CB_HANDLED; | |
68 | ||
69 | if (ts->raw_level < 0) | |
70 | edge = "Unknown"; | |
71 | ||
72 | pr_info("HW timestamp(%u: %llu): %llu, edge: %s\n", | |
73 | desc->attr.line_id, ts->seq, ts->tsc, | |
74 | (ts->raw_level >= 0) ? ((ts->raw_level == 0) ? | |
75 | "falling" : "rising") : edge); | |
76 | ||
77 | return HTE_CB_HANDLED; | |
78 | } | |
79 | ||
80 | static void gpio_timer_cb(struct timer_list *t) | |
81 | { | |
82 | (void)t; | |
83 | ||
84 | gpiod_set_value(hte.gpio_out, !gpiod_get_value(hte.gpio_out)); | |
85 | mod_timer(&hte.timer, jiffies + msecs_to_jiffies(8000)); | |
86 | } | |
87 | ||
88 | static irqreturn_t tegra_hte_test_gpio_isr(int irq, void *data) | |
89 | { | |
90 | (void)irq; | |
91 | (void)data; | |
92 | ||
93 | return IRQ_HANDLED; | |
94 | } | |
95 | ||
96 | static const struct of_device_id tegra_hte_test_of_match[] = { | |
97 | { .compatible = "nvidia,tegra194-hte-test"}, | |
98 | { } | |
99 | }; | |
100 | MODULE_DEVICE_TABLE(of, tegra_hte_test_of_match); | |
101 | ||
102 | static int tegra_hte_test_probe(struct platform_device *pdev) | |
103 | { | |
104 | int ret = 0; | |
105 | int i, cnt; | |
106 | ||
107 | dev_set_drvdata(&pdev->dev, &hte); | |
108 | hte.pdev = &pdev->dev; | |
109 | ||
110 | hte.gpio_out = gpiod_get(&pdev->dev, "out", 0); | |
111 | if (IS_ERR(hte.gpio_out)) { | |
112 | dev_err(&pdev->dev, "failed to get gpio out\n"); | |
113 | ret = -EINVAL; | |
114 | goto out; | |
115 | } | |
116 | ||
117 | hte.gpio_in = gpiod_get(&pdev->dev, "in", 0); | |
118 | if (IS_ERR(hte.gpio_in)) { | |
119 | dev_err(&pdev->dev, "failed to get gpio in\n"); | |
120 | ret = -EINVAL; | |
121 | goto free_gpio_out; | |
122 | } | |
123 | ||
124 | ret = gpiod_direction_output(hte.gpio_out, 0); | |
125 | if (ret) { | |
126 | dev_err(&pdev->dev, "failed to set output\n"); | |
127 | ret = -EINVAL; | |
128 | goto free_gpio_in; | |
129 | } | |
130 | ||
131 | ret = gpiod_direction_input(hte.gpio_in); | |
132 | if (ret) { | |
133 | dev_err(&pdev->dev, "failed to set input\n"); | |
134 | ret = -EINVAL; | |
135 | goto free_gpio_in; | |
136 | } | |
137 | ||
138 | ret = gpiod_to_irq(hte.gpio_in); | |
139 | if (ret < 0) { | |
140 | dev_err(&pdev->dev, "failed to map GPIO to IRQ: %d\n", ret); | |
141 | ret = -ENXIO; | |
142 | goto free_gpio_in; | |
143 | } | |
144 | ||
145 | hte.gpio_in_irq = ret; | |
146 | ret = request_irq(ret, tegra_hte_test_gpio_isr, | |
147 | IRQF_TRIGGER_RISING, | |
148 | "tegra_hte_gpio_test_isr", &hte); | |
149 | if (ret) { | |
150 | dev_err(&pdev->dev, "failed to acquire IRQ\n"); | |
151 | ret = -ENXIO; | |
152 | goto free_irq; | |
153 | } | |
154 | ||
155 | cnt = of_hte_req_count(hte.pdev); | |
156 | if (cnt < 0) | |
157 | goto free_irq; | |
158 | ||
159 | dev_info(&pdev->dev, "Total requested lines:%d\n", cnt); | |
160 | ||
161 | hte.desc = devm_kzalloc(hte.pdev, sizeof(*hte.desc) * cnt, GFP_KERNEL); | |
162 | if (!hte.desc) { | |
163 | ret = -ENOMEM; | |
164 | goto free_irq; | |
165 | } | |
166 | ||
167 | for (i = 0; i < cnt; i++) { | |
168 | if (i == 0) | |
169 | /* | |
170 | * GPIO hte init, line_id and name will be parsed from | |
171 | * the device tree node. The edge_flag is implicitly | |
172 | * set by request_irq call. Only line_data is needed to be | |
173 | * set. | |
174 | */ | |
175 | hte_init_line_attr(&hte.desc[i], 0, 0, NULL, | |
176 | hte.gpio_in); | |
177 | else | |
178 | /* | |
179 | * same comment as above except that IRQ does not need | |
180 | * line data. | |
181 | */ | |
182 | hte_init_line_attr(&hte.desc[i], 0, 0, NULL, NULL); | |
183 | ||
184 | ret = hte_ts_get(hte.pdev, &hte.desc[i], i); | |
185 | if (ret) | |
186 | goto ts_put; | |
187 | ||
188 | ret = devm_hte_request_ts_ns(hte.pdev, &hte.desc[i], | |
189 | process_hw_ts, NULL, | |
190 | &hte.desc[i]); | |
191 | if (ret) /* no need to ts_put, request API takes care */ | |
192 | goto free_irq; | |
193 | } | |
194 | ||
195 | timer_setup(&hte.timer, gpio_timer_cb, 0); | |
196 | mod_timer(&hte.timer, jiffies + msecs_to_jiffies(5000)); | |
197 | ||
198 | return 0; | |
199 | ||
200 | ts_put: | |
201 | cnt = i; | |
202 | for (i = 0; i < cnt; i++) | |
203 | hte_ts_put(&hte.desc[i]); | |
204 | free_irq: | |
205 | free_irq(hte.gpio_in_irq, &hte); | |
206 | free_gpio_in: | |
207 | gpiod_put(hte.gpio_in); | |
208 | free_gpio_out: | |
209 | gpiod_put(hte.gpio_out); | |
210 | out: | |
211 | ||
212 | return ret; | |
213 | } | |
214 | ||
215 | static int tegra_hte_test_remove(struct platform_device *pdev) | |
216 | { | |
217 | (void)pdev; | |
218 | ||
219 | free_irq(hte.gpio_in_irq, &hte); | |
220 | gpiod_put(hte.gpio_in); | |
221 | gpiod_put(hte.gpio_out); | |
0668e8cc | 222 | del_timer_sync(&hte.timer); |
9a75a7cd DP |
223 | |
224 | return 0; | |
225 | } | |
226 | ||
227 | static struct platform_driver tegra_hte_test_driver = { | |
228 | .probe = tegra_hte_test_probe, | |
229 | .remove = tegra_hte_test_remove, | |
230 | .driver = { | |
231 | .name = "tegra_hte_test", | |
232 | .of_match_table = tegra_hte_test_of_match, | |
233 | }, | |
234 | }; | |
235 | module_platform_driver(tegra_hte_test_driver); | |
236 | ||
237 | MODULE_AUTHOR("Dipen Patel <dipenp@nvidia.com>"); | |
238 | MODULE_LICENSE("GPL"); |