linux/drivers/gnss/serial.c
<<
>>
Prefs
   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
  22static 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
  43err_close:
  44        serdev_device_close(serdev);
  45
  46        return ret;
  47}
  48
  49static 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
  59static 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
  77static 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
  83static 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
  92static 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
  97static 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 */
 110static 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
 123struct 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
 155err_put_device:
 156        gnss_put_device(gserial->gdev);
 157err_free_gserial:
 158        kfree(gserial);
 159
 160        return ERR_PTR(ret);
 161}
 162EXPORT_SYMBOL_GPL(gnss_serial_allocate);
 163
 164void gnss_serial_free(struct gnss_serial *gserial)
 165{
 166        gnss_put_device(gserial->gdev);
 167        kfree(gserial);
 168};
 169EXPORT_SYMBOL_GPL(gnss_serial_free);
 170
 171int 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
 190err_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}
 198EXPORT_SYMBOL_GPL(gnss_serial_register);
 199
 200void 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}
 211EXPORT_SYMBOL_GPL(gnss_serial_deregister);
 212
 213#ifdef CONFIG_PM
 214static 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
 221static 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
 229static 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
 238static 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
 255static 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
 267const 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};
 272EXPORT_SYMBOL_GPL(gnss_serial_pm_ops);
 273
 274MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
 275MODULE_DESCRIPTION("Generic serial GNSS receiver driver");
 276MODULE_LICENSE("GPL v2");
 277