Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/gnss/serial.c
26278 views
1
// SPDX-License-Identifier: GPL-2.0
2
/*
3
* Generic serial GNSS receiver driver
4
*
5
* Copyright (C) 2018 Johan Hovold <[email protected]>
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(&serdev->dev);
36
if (ret < 0) {
37
pm_runtime_put_noidle(&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(&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 size_t gnss_serial_receive_buf(struct serdev_device *serdev,
84
const u8 *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(node, "current-speed", &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(sizeof(*gserial) + data_size, GFP_KERNEL);
131
if (!gserial)
132
return ERR_PTR(-ENOMEM);
133
134
gdev = gnss_allocate_device(&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, gserial);
142
143
gserial->serdev = serdev;
144
gserial->gdev = gdev;
145
146
serdev_device_set_drvdata(serdev, gserial);
147
serdev_device_set_client_ops(serdev, &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(gserial->gdev);
157
err_free_gserial:
158
kfree(gserial);
159
160
return ERR_PTR(ret);
161
}
162
EXPORT_SYMBOL_GPL(gnss_serial_allocate);
163
164
void gnss_serial_free(struct gnss_serial *gserial)
165
{
166
gnss_put_device(gserial->gdev);
167
kfree(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(&serdev->dev);
178
} else {
179
ret = gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE);
180
if (ret < 0)
181
return ret;
182
}
183
184
ret = gnss_register_device(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(&serdev->dev);
193
else
194
gnss_serial_set_power(gserial, 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(gserial->gdev);
205
206
if (IS_ENABLED(CONFIG_PM))
207
pm_runtime_disable(&serdev->dev);
208
else
209
gnss_serial_set_power(gserial, 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, 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, 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, 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, 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 <[email protected]>");
275
MODULE_DESCRIPTION("Generic serial GNSS receiver driver");
276
MODULE_LICENSE("GPL v2");
277
278