Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Johan Hovold and committed by
Greg Kroah-Hartman
37768b05 98ddec80

+332
+7
drivers/gnss/Kconfig
··· 9 9 10 10 To compile this driver as a module, choose M here: the module will 11 11 be called gnss. 12 + 13 + if GNSS 14 + 15 + config GNSS_SERIAL 16 + tristate 17 + 18 + endif # GNSS
+3
drivers/gnss/Makefile
··· 5 5 6 6 obj-$(CONFIG_GNSS) += gnss.o 7 7 gnss-y := core.o 8 + 9 + obj-$(CONFIG_GNSS_SERIAL) += gnss-serial.o 10 + gnss-serial-y := serial.o
+275
drivers/gnss/serial.c
··· 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");
+47
drivers/gnss/serial.h
··· 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 */