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 | |
8 | #include <linux/err.h> |
9 | #include <linux/gpio/consumer.h> |
10 | #include <linux/hte.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/mod_devicetable.h> |
13 | #include <linux/module.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/timer.h> |
16 | #include <linux/workqueue.h> |
17 | |
18 | /* |
19 | * This sample HTE 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(desc: hte.gpio_out, value: !gpiod_get_value(desc: hte.gpio_out)); |
85 | mod_timer(timer: &hte.timer, expires: jiffies + msecs_to_jiffies(m: 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(dev: &pdev->dev, data: &hte); |
108 | hte.pdev = &pdev->dev; |
109 | |
110 | hte.gpio_out = gpiod_get(dev: &pdev->dev, con_id: "out" , flags: 0); |
111 | if (IS_ERR(ptr: 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(dev: &pdev->dev, con_id: "in" , flags: 0); |
118 | if (IS_ERR(ptr: 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(desc: hte.gpio_out, value: 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(desc: 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(desc: 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(irq: ret, handler: tegra_hte_test_gpio_isr, |
147 | IRQF_TRIGGER_RISING, |
148 | name: "tegra_hte_gpio_test_isr" , dev: &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(dev: hte.pdev); |
156 | if (cnt < 0) { |
157 | ret = cnt; |
158 | goto free_irq; |
159 | } |
160 | |
161 | dev_info(&pdev->dev, "Total requested lines:%d\n" , cnt); |
162 | |
163 | hte.desc = devm_kzalloc(dev: hte.pdev, size: sizeof(*hte.desc) * cnt, GFP_KERNEL); |
164 | if (!hte.desc) { |
165 | ret = -ENOMEM; |
166 | goto free_irq; |
167 | } |
168 | |
169 | for (i = 0; i < cnt; i++) { |
170 | if (i == 0) |
171 | /* |
172 | * GPIO hte init, line_id and name will be parsed from |
173 | * the device tree node. The edge_flag is implicitly |
174 | * set by request_irq call. Only line_data is needed to be |
175 | * set. |
176 | */ |
177 | hte_init_line_attr(desc: &hte.desc[i], line_id: 0, edge_flags: 0, NULL, |
178 | data: hte.gpio_in); |
179 | else |
180 | /* |
181 | * same comment as above except that IRQ does not need |
182 | * line data. |
183 | */ |
184 | hte_init_line_attr(desc: &hte.desc[i], line_id: 0, edge_flags: 0, NULL, NULL); |
185 | |
186 | ret = hte_ts_get(dev: hte.pdev, desc: &hte.desc[i], index: i); |
187 | if (ret) |
188 | goto ts_put; |
189 | |
190 | ret = devm_hte_request_ts_ns(dev: hte.pdev, desc: &hte.desc[i], |
191 | cb: process_hw_ts, NULL, |
192 | data: &hte.desc[i]); |
193 | if (ret) /* no need to ts_put, request API takes care */ |
194 | goto free_irq; |
195 | } |
196 | |
197 | timer_setup(&hte.timer, gpio_timer_cb, 0); |
198 | mod_timer(timer: &hte.timer, expires: jiffies + msecs_to_jiffies(m: 5000)); |
199 | |
200 | return 0; |
201 | |
202 | ts_put: |
203 | cnt = i; |
204 | for (i = 0; i < cnt; i++) |
205 | hte_ts_put(desc: &hte.desc[i]); |
206 | free_irq: |
207 | free_irq(hte.gpio_in_irq, &hte); |
208 | free_gpio_in: |
209 | gpiod_put(desc: hte.gpio_in); |
210 | free_gpio_out: |
211 | gpiod_put(desc: hte.gpio_out); |
212 | out: |
213 | |
214 | return ret; |
215 | } |
216 | |
217 | static int tegra_hte_test_remove(struct platform_device *pdev) |
218 | { |
219 | (void)pdev; |
220 | |
221 | free_irq(hte.gpio_in_irq, &hte); |
222 | gpiod_put(desc: hte.gpio_in); |
223 | gpiod_put(desc: hte.gpio_out); |
224 | del_timer_sync(timer: &hte.timer); |
225 | |
226 | return 0; |
227 | } |
228 | |
229 | static struct platform_driver tegra_hte_test_driver = { |
230 | .probe = tegra_hte_test_probe, |
231 | .remove = tegra_hte_test_remove, |
232 | .driver = { |
233 | .name = "tegra_hte_test" , |
234 | .of_match_table = tegra_hte_test_of_match, |
235 | }, |
236 | }; |
237 | module_platform_driver(tegra_hte_test_driver); |
238 | |
239 | MODULE_AUTHOR("Dipen Patel <dipenp@nvidia.com>" ); |
240 | MODULE_LICENSE("GPL" ); |
241 | |