1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Generic serial GNSS receiver driver |
4 | * |
5 | * Copyright (C) 2018 Johan Hovold <johan@kernel.org> |
6 | */ |
7 | |
8 | #include <linux/errno.h> |
9 | #include <linux/gnss.h> |
10 | #include <linux/init.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/pm.h> |
15 | #include <linux/pm_runtime.h> |
16 | #include <linux/sched.h> |
17 | #include <linux/serdev.h> |
18 | #include <linux/slab.h> |
19 | |
20 | #include "serial.h" |
21 | |
22 | static int gnss_serial_open(struct gnss_device *gdev) |
23 | { |
24 | struct gnss_serial *gserial = gnss_get_drvdata(gdev); |
25 | struct serdev_device *serdev = gserial->serdev; |
26 | int ret; |
27 | |
28 | ret = serdev_device_open(serdev); |
29 | if (ret) |
30 | return ret; |
31 | |
32 | serdev_device_set_baudrate(serdev, gserial->speed); |
33 | serdev_device_set_flow_control(serdev, false); |
34 | |
35 | ret = pm_runtime_get_sync(dev: &serdev->dev); |
36 | if (ret < 0) { |
37 | pm_runtime_put_noidle(dev: &serdev->dev); |
38 | goto err_close; |
39 | } |
40 | |
41 | return 0; |
42 | |
43 | err_close: |
44 | serdev_device_close(serdev); |
45 | |
46 | return ret; |
47 | } |
48 | |
49 | static void gnss_serial_close(struct gnss_device *gdev) |
50 | { |
51 | struct gnss_serial *gserial = gnss_get_drvdata(gdev); |
52 | struct serdev_device *serdev = gserial->serdev; |
53 | |
54 | serdev_device_close(serdev); |
55 | |
56 | pm_runtime_put(dev: &serdev->dev); |
57 | } |
58 | |
59 | static int gnss_serial_write_raw(struct gnss_device *gdev, |
60 | const unsigned char *buf, size_t count) |
61 | { |
62 | struct gnss_serial *gserial = gnss_get_drvdata(gdev); |
63 | struct serdev_device *serdev = gserial->serdev; |
64 | int ret; |
65 | |
66 | /* write is only buffered synchronously */ |
67 | ret = serdev_device_write(serdev, buf, count, MAX_SCHEDULE_TIMEOUT); |
68 | if (ret < 0 || ret < count) |
69 | return ret; |
70 | |
71 | /* FIXME: determine if interrupted? */ |
72 | serdev_device_wait_until_sent(serdev, 0); |
73 | |
74 | return count; |
75 | } |
76 | |
77 | static const struct gnss_operations gnss_serial_gnss_ops = { |
78 | .open = gnss_serial_open, |
79 | .close = gnss_serial_close, |
80 | .write_raw = gnss_serial_write_raw, |
81 | }; |
82 | |
83 | static int gnss_serial_receive_buf(struct serdev_device *serdev, |
84 | const unsigned char *buf, size_t count) |
85 | { |
86 | struct gnss_serial *gserial = serdev_device_get_drvdata(serdev); |
87 | struct gnss_device *gdev = gserial->gdev; |
88 | |
89 | return gnss_insert_raw(gdev, buf, count); |
90 | } |
91 | |
92 | static const struct serdev_device_ops gnss_serial_serdev_ops = { |
93 | .receive_buf = gnss_serial_receive_buf, |
94 | .write_wakeup = serdev_device_write_wakeup, |
95 | }; |
96 | |
97 | static int gnss_serial_set_power(struct gnss_serial *gserial, |
98 | enum gnss_serial_pm_state state) |
99 | { |
100 | if (!gserial->ops || !gserial->ops->set_power) |
101 | return 0; |
102 | |
103 | return gserial->ops->set_power(gserial, state); |
104 | } |
105 | |
106 | /* |
107 | * FIXME: need to provide subdriver defaults or separate dt parsing from |
108 | * allocation. |
109 | */ |
110 | static int gnss_serial_parse_dt(struct serdev_device *serdev) |
111 | { |
112 | struct gnss_serial *gserial = serdev_device_get_drvdata(serdev); |
113 | struct device_node *node = serdev->dev.of_node; |
114 | u32 speed = 4800; |
115 | |
116 | of_property_read_u32(np: node, propname: "current-speed" , out_value: &speed); |
117 | |
118 | gserial->speed = speed; |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | struct gnss_serial *gnss_serial_allocate(struct serdev_device *serdev, |
124 | size_t data_size) |
125 | { |
126 | struct gnss_serial *gserial; |
127 | struct gnss_device *gdev; |
128 | int ret; |
129 | |
130 | gserial = kzalloc(size: sizeof(*gserial) + data_size, GFP_KERNEL); |
131 | if (!gserial) |
132 | return ERR_PTR(error: -ENOMEM); |
133 | |
134 | gdev = gnss_allocate_device(parent: &serdev->dev); |
135 | if (!gdev) { |
136 | ret = -ENOMEM; |
137 | goto err_free_gserial; |
138 | } |
139 | |
140 | gdev->ops = &gnss_serial_gnss_ops; |
141 | gnss_set_drvdata(gdev, data: gserial); |
142 | |
143 | gserial->serdev = serdev; |
144 | gserial->gdev = gdev; |
145 | |
146 | serdev_device_set_drvdata(serdev, data: gserial); |
147 | serdev_device_set_client_ops(serdev, ops: &gnss_serial_serdev_ops); |
148 | |
149 | ret = gnss_serial_parse_dt(serdev); |
150 | if (ret) |
151 | goto err_put_device; |
152 | |
153 | return gserial; |
154 | |
155 | err_put_device: |
156 | gnss_put_device(gdev: gserial->gdev); |
157 | err_free_gserial: |
158 | kfree(objp: gserial); |
159 | |
160 | return ERR_PTR(error: ret); |
161 | } |
162 | EXPORT_SYMBOL_GPL(gnss_serial_allocate); |
163 | |
164 | void gnss_serial_free(struct gnss_serial *gserial) |
165 | { |
166 | gnss_put_device(gdev: gserial->gdev); |
167 | kfree(objp: gserial); |
168 | } |
169 | EXPORT_SYMBOL_GPL(gnss_serial_free); |
170 | |
171 | int gnss_serial_register(struct gnss_serial *gserial) |
172 | { |
173 | struct serdev_device *serdev = gserial->serdev; |
174 | int ret; |
175 | |
176 | if (IS_ENABLED(CONFIG_PM)) { |
177 | pm_runtime_enable(dev: &serdev->dev); |
178 | } else { |
179 | ret = gnss_serial_set_power(gserial, state: GNSS_SERIAL_ACTIVE); |
180 | if (ret < 0) |
181 | return ret; |
182 | } |
183 | |
184 | ret = gnss_register_device(gdev: gserial->gdev); |
185 | if (ret) |
186 | goto err_disable_rpm; |
187 | |
188 | return 0; |
189 | |
190 | err_disable_rpm: |
191 | if (IS_ENABLED(CONFIG_PM)) |
192 | pm_runtime_disable(dev: &serdev->dev); |
193 | else |
194 | gnss_serial_set_power(gserial, state: GNSS_SERIAL_OFF); |
195 | |
196 | return ret; |
197 | } |
198 | EXPORT_SYMBOL_GPL(gnss_serial_register); |
199 | |
200 | void gnss_serial_deregister(struct gnss_serial *gserial) |
201 | { |
202 | struct serdev_device *serdev = gserial->serdev; |
203 | |
204 | gnss_deregister_device(gdev: gserial->gdev); |
205 | |
206 | if (IS_ENABLED(CONFIG_PM)) |
207 | pm_runtime_disable(dev: &serdev->dev); |
208 | else |
209 | gnss_serial_set_power(gserial, state: GNSS_SERIAL_OFF); |
210 | } |
211 | EXPORT_SYMBOL_GPL(gnss_serial_deregister); |
212 | |
213 | #ifdef CONFIG_PM |
214 | static int gnss_serial_runtime_suspend(struct device *dev) |
215 | { |
216 | struct gnss_serial *gserial = dev_get_drvdata(dev); |
217 | |
218 | return gnss_serial_set_power(gserial, state: GNSS_SERIAL_STANDBY); |
219 | } |
220 | |
221 | static int gnss_serial_runtime_resume(struct device *dev) |
222 | { |
223 | struct gnss_serial *gserial = dev_get_drvdata(dev); |
224 | |
225 | return gnss_serial_set_power(gserial, state: GNSS_SERIAL_ACTIVE); |
226 | } |
227 | #endif /* CONFIG_PM */ |
228 | |
229 | static int gnss_serial_prepare(struct device *dev) |
230 | { |
231 | if (pm_runtime_suspended(dev)) |
232 | return 1; |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | #ifdef CONFIG_PM_SLEEP |
238 | static int gnss_serial_suspend(struct device *dev) |
239 | { |
240 | struct gnss_serial *gserial = dev_get_drvdata(dev); |
241 | int ret = 0; |
242 | |
243 | /* |
244 | * FIXME: serdev currently lacks support for managing the underlying |
245 | * device's wakeup settings. A workaround would be to close the serdev |
246 | * device here if it is open. |
247 | */ |
248 | |
249 | if (!pm_runtime_suspended(dev)) |
250 | ret = gnss_serial_set_power(gserial, state: GNSS_SERIAL_STANDBY); |
251 | |
252 | return ret; |
253 | } |
254 | |
255 | static int gnss_serial_resume(struct device *dev) |
256 | { |
257 | struct gnss_serial *gserial = dev_get_drvdata(dev); |
258 | int ret = 0; |
259 | |
260 | if (!pm_runtime_suspended(dev)) |
261 | ret = gnss_serial_set_power(gserial, state: GNSS_SERIAL_ACTIVE); |
262 | |
263 | return ret; |
264 | } |
265 | #endif /* CONFIG_PM_SLEEP */ |
266 | |
267 | const struct dev_pm_ops gnss_serial_pm_ops = { |
268 | .prepare = gnss_serial_prepare, |
269 | SET_SYSTEM_SLEEP_PM_OPS(gnss_serial_suspend, gnss_serial_resume) |
270 | SET_RUNTIME_PM_OPS(gnss_serial_runtime_suspend, gnss_serial_runtime_resume, NULL) |
271 | }; |
272 | EXPORT_SYMBOL_GPL(gnss_serial_pm_ops); |
273 | |
274 | MODULE_AUTHOR("Johan Hovold <johan@kernel.org>" ); |
275 | MODULE_DESCRIPTION("Generic serial GNSS receiver driver" ); |
276 | MODULE_LICENSE("GPL v2" ); |
277 | |