diff options
author | Johan Hovold <johan@kernel.org> | 2018-06-01 04:22:54 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-06-28 07:31:32 -0400 |
commit | 37768b054f2074f40de3cacd492baed482f5d9da (patch) | |
tree | 35d002a267bd214615d77631e16426dc9e65e41d | |
parent | 98ddec80fdf1c3e6c594fe633e6fb2a8a0d699dd (diff) |
gnss: add generic serial driver
Add a generic serial GNSS driver (library) which provides a common
implementation for the gnss interface and power management (runtime and
system suspend). This allows GNSS drivers for specific chip to be
implemented by simply providing a set_power() callback to handle three
states: ACTIVE, STANDBY and OFF.
Signed-off-by: Johan Hovold <johan@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/gnss/Kconfig | 7 | ||||
-rw-r--r-- | drivers/gnss/Makefile | 3 | ||||
-rw-r--r-- | drivers/gnss/serial.c | 275 | ||||
-rw-r--r-- | drivers/gnss/serial.h | 47 |
4 files changed, 332 insertions, 0 deletions
diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig index 103fcc70992e..f8ee54f99a8d 100644 --- a/drivers/gnss/Kconfig +++ b/drivers/gnss/Kconfig | |||
@@ -9,3 +9,10 @@ menuconfig GNSS | |||
9 | 9 | ||
10 | To compile this driver as a module, choose M here: the module will | 10 | To compile this driver as a module, choose M here: the module will |
11 | be called gnss. | 11 | be called gnss. |
12 | |||
13 | if GNSS | ||
14 | |||
15 | config GNSS_SERIAL | ||
16 | tristate | ||
17 | |||
18 | endif # GNSS | ||
diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile index 1f7a7baab1d9..171aba71684d 100644 --- a/drivers/gnss/Makefile +++ b/drivers/gnss/Makefile | |||
@@ -5,3 +5,6 @@ | |||
5 | 5 | ||
6 | obj-$(CONFIG_GNSS) += gnss.o | 6 | obj-$(CONFIG_GNSS) += gnss.o |
7 | gnss-y := core.o | 7 | gnss-y := core.o |
8 | |||
9 | obj-$(CONFIG_GNSS_SERIAL) += gnss-serial.o | ||
10 | gnss-serial-y := serial.o | ||
diff --git a/drivers/gnss/serial.c b/drivers/gnss/serial.c new file mode 100644 index 000000000000..b01ba4438501 --- /dev/null +++ b/drivers/gnss/serial.c | |||
@@ -0,0 +1,275 @@ | |||
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/serdev.h> | ||
17 | #include <linux/slab.h> | ||
18 | |||
19 | #include "serial.h" | ||
20 | |||
21 | static int gnss_serial_open(struct gnss_device *gdev) | ||
22 | { | ||
23 | struct gnss_serial *gserial = gnss_get_drvdata(gdev); | ||
24 | struct serdev_device *serdev = gserial->serdev; | ||
25 | int ret; | ||
26 | |||
27 | ret = serdev_device_open(serdev); | ||
28 | if (ret) | ||
29 | return ret; | ||
30 | |||
31 | serdev_device_set_baudrate(serdev, gserial->speed); | ||
32 | serdev_device_set_flow_control(serdev, false); | ||
33 | |||
34 | ret = pm_runtime_get_sync(&serdev->dev); | ||
35 | if (ret < 0) { | ||
36 | pm_runtime_put_noidle(&serdev->dev); | ||
37 | goto err_close; | ||
38 | } | ||
39 | |||
40 | return 0; | ||
41 | |||
42 | err_close: | ||
43 | serdev_device_close(serdev); | ||
44 | |||
45 | return ret; | ||
46 | } | ||
47 | |||
48 | static void gnss_serial_close(struct gnss_device *gdev) | ||
49 | { | ||
50 | struct gnss_serial *gserial = gnss_get_drvdata(gdev); | ||
51 | struct serdev_device *serdev = gserial->serdev; | ||
52 | |||
53 | serdev_device_close(serdev); | ||
54 | |||
55 | pm_runtime_put(&serdev->dev); | ||
56 | } | ||
57 | |||
58 | static int gnss_serial_write_raw(struct gnss_device *gdev, | ||
59 | const unsigned char *buf, size_t count) | ||
60 | { | ||
61 | struct gnss_serial *gserial = gnss_get_drvdata(gdev); | ||
62 | struct serdev_device *serdev = gserial->serdev; | ||
63 | int ret; | ||
64 | |||
65 | /* write is only buffered synchronously */ | ||
66 | ret = serdev_device_write(serdev, buf, count, 0); | ||
67 | if (ret < 0) | ||
68 | return ret; | ||
69 | |||
70 | /* FIXME: determine if interrupted? */ | ||
71 | serdev_device_wait_until_sent(serdev, 0); | ||
72 | |||
73 | return count; | ||
74 | } | ||
75 | |||
76 | static const struct gnss_operations gnss_serial_gnss_ops = { | ||
77 | .open = gnss_serial_open, | ||
78 | .close = gnss_serial_close, | ||
79 | .write_raw = gnss_serial_write_raw, | ||
80 | }; | ||
81 | |||
82 | static int gnss_serial_receive_buf(struct serdev_device *serdev, | ||
83 | const unsigned char *buf, size_t count) | ||
84 | { | ||
85 | struct gnss_serial *gserial = serdev_device_get_drvdata(serdev); | ||
86 | struct gnss_device *gdev = gserial->gdev; | ||
87 | |||
88 | return gnss_insert_raw(gdev, buf, count); | ||
89 | } | ||
90 | |||
91 | static const struct serdev_device_ops gnss_serial_serdev_ops = { | ||
92 | .receive_buf = gnss_serial_receive_buf, | ||
93 | .write_wakeup = serdev_device_write_wakeup, | ||
94 | }; | ||
95 | |||
96 | static int gnss_serial_set_power(struct gnss_serial *gserial, | ||
97 | enum gnss_serial_pm_state state) | ||
98 | { | ||
99 | if (!gserial->ops || !gserial->ops->set_power) | ||
100 | return 0; | ||
101 | |||
102 | return gserial->ops->set_power(gserial, state); | ||
103 | } | ||
104 | |||
105 | /* | ||
106 | * FIXME: need to provide subdriver defaults or separate dt parsing from | ||
107 | * allocation. | ||
108 | */ | ||
109 | static int gnss_serial_parse_dt(struct serdev_device *serdev) | ||
110 | { | ||
111 | struct gnss_serial *gserial = serdev_device_get_drvdata(serdev); | ||
112 | struct device_node *node = serdev->dev.of_node; | ||
113 | u32 speed = 4800; | ||
114 | |||
115 | of_property_read_u32(node, "current-speed", &speed); | ||
116 | |||
117 | gserial->speed = speed; | ||
118 | |||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | struct gnss_serial *gnss_serial_allocate(struct serdev_device *serdev, | ||
123 | size_t data_size) | ||
124 | { | ||
125 | struct gnss_serial *gserial; | ||
126 | struct gnss_device *gdev; | ||
127 | int ret; | ||
128 | |||
129 | gserial = kzalloc(sizeof(*gserial) + data_size, GFP_KERNEL); | ||
130 | if (!gserial) | ||
131 | return ERR_PTR(-ENOMEM); | ||
132 | |||
133 | gdev = gnss_allocate_device(&serdev->dev); | ||
134 | if (!gdev) { | ||
135 | ret = -ENOMEM; | ||
136 | goto err_free_gserial; | ||
137 | } | ||
138 | |||
139 | gdev->ops = &gnss_serial_gnss_ops; | ||
140 | gnss_set_drvdata(gdev, gserial); | ||
141 | |||
142 | gserial->serdev = serdev; | ||
143 | gserial->gdev = gdev; | ||
144 | |||
145 | serdev_device_set_drvdata(serdev, gserial); | ||
146 | serdev_device_set_client_ops(serdev, &gnss_serial_serdev_ops); | ||
147 | |||
148 | ret = gnss_serial_parse_dt(serdev); | ||
149 | if (ret) | ||
150 | goto err_put_device; | ||
151 | |||
152 | return gserial; | ||
153 | |||
154 | err_put_device: | ||
155 | gnss_put_device(gserial->gdev); | ||
156 | err_free_gserial: | ||
157 | kfree(gserial); | ||
158 | |||
159 | return ERR_PTR(ret); | ||
160 | } | ||
161 | EXPORT_SYMBOL_GPL(gnss_serial_allocate); | ||
162 | |||
163 | void gnss_serial_free(struct gnss_serial *gserial) | ||
164 | { | ||
165 | gnss_put_device(gserial->gdev); | ||
166 | kfree(gserial); | ||
167 | }; | ||
168 | EXPORT_SYMBOL_GPL(gnss_serial_free); | ||
169 | |||
170 | int gnss_serial_register(struct gnss_serial *gserial) | ||
171 | { | ||
172 | struct serdev_device *serdev = gserial->serdev; | ||
173 | int ret; | ||
174 | |||
175 | if (IS_ENABLED(CONFIG_PM)) { | ||
176 | pm_runtime_enable(&serdev->dev); | ||
177 | } else { | ||
178 | ret = gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE); | ||
179 | if (ret < 0) | ||
180 | return ret; | ||
181 | } | ||
182 | |||
183 | ret = gnss_register_device(gserial->gdev); | ||
184 | if (ret) | ||
185 | goto err_disable_rpm; | ||
186 | |||
187 | return 0; | ||
188 | |||
189 | err_disable_rpm: | ||
190 | if (IS_ENABLED(CONFIG_PM)) | ||
191 | pm_runtime_disable(&serdev->dev); | ||
192 | else | ||
193 | gnss_serial_set_power(gserial, GNSS_SERIAL_OFF); | ||
194 | |||
195 | return ret; | ||
196 | } | ||
197 | EXPORT_SYMBOL_GPL(gnss_serial_register); | ||
198 | |||
199 | void gnss_serial_deregister(struct gnss_serial *gserial) | ||
200 | { | ||
201 | struct serdev_device *serdev = gserial->serdev; | ||
202 | |||
203 | gnss_deregister_device(gserial->gdev); | ||
204 | |||
205 | if (IS_ENABLED(CONFIG_PM)) | ||
206 | pm_runtime_disable(&serdev->dev); | ||
207 | else | ||
208 | gnss_serial_set_power(gserial, GNSS_SERIAL_OFF); | ||
209 | } | ||
210 | EXPORT_SYMBOL_GPL(gnss_serial_deregister); | ||
211 | |||
212 | #ifdef CONFIG_PM | ||
213 | static int gnss_serial_runtime_suspend(struct device *dev) | ||
214 | { | ||
215 | struct gnss_serial *gserial = dev_get_drvdata(dev); | ||
216 | |||
217 | return gnss_serial_set_power(gserial, GNSS_SERIAL_STANDBY); | ||
218 | } | ||
219 | |||
220 | static int gnss_serial_runtime_resume(struct device *dev) | ||
221 | { | ||
222 | struct gnss_serial *gserial = dev_get_drvdata(dev); | ||
223 | |||
224 | return gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE); | ||
225 | } | ||
226 | #endif /* CONFIG_PM */ | ||
227 | |||
228 | static int gnss_serial_prepare(struct device *dev) | ||
229 | { | ||
230 | if (pm_runtime_suspended(dev)) | ||
231 | return 1; | ||
232 | |||
233 | return 0; | ||
234 | } | ||
235 | |||
236 | #ifdef CONFIG_PM_SLEEP | ||
237 | static int gnss_serial_suspend(struct device *dev) | ||
238 | { | ||
239 | struct gnss_serial *gserial = dev_get_drvdata(dev); | ||
240 | int ret = 0; | ||
241 | |||
242 | /* | ||
243 | * FIXME: serdev currently lacks support for managing the underlying | ||
244 | * device's wakeup settings. A workaround would be to close the serdev | ||
245 | * device here if it is open. | ||
246 | */ | ||
247 | |||
248 | if (!pm_runtime_suspended(dev)) | ||
249 | ret = gnss_serial_set_power(gserial, GNSS_SERIAL_STANDBY); | ||
250 | |||
251 | return ret; | ||
252 | } | ||
253 | |||
254 | static int gnss_serial_resume(struct device *dev) | ||
255 | { | ||
256 | struct gnss_serial *gserial = dev_get_drvdata(dev); | ||
257 | int ret = 0; | ||
258 | |||
259 | if (!pm_runtime_suspended(dev)) | ||
260 | ret = gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE); | ||
261 | |||
262 | return ret; | ||
263 | } | ||
264 | #endif /* CONFIG_PM_SLEEP */ | ||
265 | |||
266 | const struct dev_pm_ops gnss_serial_pm_ops = { | ||
267 | .prepare = gnss_serial_prepare, | ||
268 | SET_SYSTEM_SLEEP_PM_OPS(gnss_serial_suspend, gnss_serial_resume) | ||
269 | SET_RUNTIME_PM_OPS(gnss_serial_runtime_suspend, gnss_serial_runtime_resume, NULL) | ||
270 | }; | ||
271 | EXPORT_SYMBOL_GPL(gnss_serial_pm_ops); | ||
272 | |||
273 | MODULE_AUTHOR("Johan Hovold <johan@kernel.org>"); | ||
274 | MODULE_DESCRIPTION("Generic serial GNSS receiver driver"); | ||
275 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/gnss/serial.h b/drivers/gnss/serial.h new file mode 100644 index 000000000000..980ffdc86c2a --- /dev/null +++ b/drivers/gnss/serial.h | |||
@@ -0,0 +1,47 @@ | |||
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 | #ifndef _LINUX_GNSS_SERIAL_H | ||
9 | #define _LINUX_GNSS_SERIAL_H | ||
10 | |||
11 | #include <asm/termbits.h> | ||
12 | #include <linux/pm.h> | ||
13 | |||
14 | struct gnss_serial { | ||
15 | struct serdev_device *serdev; | ||
16 | struct gnss_device *gdev; | ||
17 | speed_t speed; | ||
18 | const struct gnss_serial_ops *ops; | ||
19 | unsigned long drvdata[0]; | ||
20 | }; | ||
21 | |||
22 | enum gnss_serial_pm_state { | ||
23 | GNSS_SERIAL_OFF, | ||
24 | GNSS_SERIAL_ACTIVE, | ||
25 | GNSS_SERIAL_STANDBY, | ||
26 | }; | ||
27 | |||
28 | struct gnss_serial_ops { | ||
29 | int (*set_power)(struct gnss_serial *gserial, | ||
30 | enum gnss_serial_pm_state state); | ||
31 | }; | ||
32 | |||
33 | extern const struct dev_pm_ops gnss_serial_pm_ops; | ||
34 | |||
35 | struct gnss_serial *gnss_serial_allocate(struct serdev_device *gserial, | ||
36 | size_t data_size); | ||
37 | void gnss_serial_free(struct gnss_serial *gserial); | ||
38 | |||
39 | int gnss_serial_register(struct gnss_serial *gserial); | ||
40 | void gnss_serial_deregister(struct gnss_serial *gserial); | ||
41 | |||
42 | static inline void *gnss_serial_get_drvdata(struct gnss_serial *gserial) | ||
43 | { | ||
44 | return gserial->drvdata; | ||
45 | } | ||
46 | |||
47 | #endif /* _LINUX_GNSS_SERIAL_H */ | ||