| // SPDX-License-Identifier: GPL-2.0 | 
| /* | 
|  * u-blox GNSS receiver driver | 
|  * | 
|  * Copyright (C) 2018 Johan Hovold <johan@kernel.org> | 
|  */ | 
|   | 
| #include <linux/errno.h> | 
| #include <linux/gnss.h> | 
| #include <linux/init.h> | 
| #include <linux/kernel.h> | 
| #include <linux/module.h> | 
| #include <linux/of.h> | 
| #include <linux/regulator/consumer.h> | 
| #include <linux/serdev.h> | 
|   | 
| #include "serial.h" | 
|   | 
| struct ubx_data { | 
|     struct regulator *v_bckp; | 
|     struct regulator *vcc; | 
| }; | 
|   | 
| static int ubx_set_active(struct gnss_serial *gserial) | 
| { | 
|     struct ubx_data *data = gnss_serial_get_drvdata(gserial); | 
|     int ret; | 
|   | 
|     ret = regulator_enable(data->vcc); | 
|     if (ret) | 
|         return ret; | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int ubx_set_standby(struct gnss_serial *gserial) | 
| { | 
|     struct ubx_data *data = gnss_serial_get_drvdata(gserial); | 
|     int ret; | 
|   | 
|     ret = regulator_disable(data->vcc); | 
|     if (ret) | 
|         return ret; | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int ubx_set_power(struct gnss_serial *gserial, | 
|                 enum gnss_serial_pm_state state) | 
| { | 
|     switch (state) { | 
|     case GNSS_SERIAL_ACTIVE: | 
|         return ubx_set_active(gserial); | 
|     case GNSS_SERIAL_OFF: | 
|     case GNSS_SERIAL_STANDBY: | 
|         return ubx_set_standby(gserial); | 
|     } | 
|   | 
|     return -EINVAL; | 
| } | 
|   | 
| static const struct gnss_serial_ops ubx_gserial_ops = { | 
|     .set_power = ubx_set_power, | 
| }; | 
|   | 
| static int ubx_probe(struct serdev_device *serdev) | 
| { | 
|     struct gnss_serial *gserial; | 
|     struct ubx_data *data; | 
|     int ret; | 
|   | 
|     gserial = gnss_serial_allocate(serdev, sizeof(*data)); | 
|     if (IS_ERR(gserial)) { | 
|         ret = PTR_ERR(gserial); | 
|         return ret; | 
|     } | 
|   | 
|     gserial->ops = &ubx_gserial_ops; | 
|   | 
|     gserial->gdev->type = GNSS_TYPE_UBX; | 
|   | 
|     data = gnss_serial_get_drvdata(gserial); | 
|   | 
|     data->vcc = devm_regulator_get(&serdev->dev, "vcc"); | 
|     if (IS_ERR(data->vcc)) { | 
|         ret = PTR_ERR(data->vcc); | 
|         goto err_free_gserial; | 
|     } | 
|   | 
|     data->v_bckp = devm_regulator_get_optional(&serdev->dev, "v-bckp"); | 
|     if (IS_ERR(data->v_bckp)) { | 
|         ret = PTR_ERR(data->v_bckp); | 
|         if (ret == -ENODEV) | 
|             data->v_bckp = NULL; | 
|         else | 
|             goto err_free_gserial; | 
|     } | 
|   | 
|     if (data->v_bckp) { | 
|         ret = regulator_enable(data->v_bckp); | 
|         if (ret) | 
|             goto err_free_gserial; | 
|     } | 
|   | 
|     ret = gnss_serial_register(gserial); | 
|     if (ret) | 
|         goto err_disable_v_bckp; | 
|   | 
|     return 0; | 
|   | 
| err_disable_v_bckp: | 
|     if (data->v_bckp) | 
|         regulator_disable(data->v_bckp); | 
| err_free_gserial: | 
|     gnss_serial_free(gserial); | 
|   | 
|     return ret; | 
| } | 
|   | 
| static void ubx_remove(struct serdev_device *serdev) | 
| { | 
|     struct gnss_serial *gserial = serdev_device_get_drvdata(serdev); | 
|     struct ubx_data *data = gnss_serial_get_drvdata(gserial); | 
|   | 
|     gnss_serial_deregister(gserial); | 
|     if (data->v_bckp) | 
|         regulator_disable(data->v_bckp); | 
|     gnss_serial_free(gserial); | 
| }; | 
|   | 
| #ifdef CONFIG_OF | 
| static const struct of_device_id ubx_of_match[] = { | 
|     { .compatible = "u-blox,neo-6m" }, | 
|     { .compatible = "u-blox,neo-8" }, | 
|     { .compatible = "u-blox,neo-m8" }, | 
|     {}, | 
| }; | 
| MODULE_DEVICE_TABLE(of, ubx_of_match); | 
| #endif | 
|   | 
| static struct serdev_device_driver ubx_driver = { | 
|     .driver    = { | 
|         .name        = "gnss-ubx", | 
|         .of_match_table    = of_match_ptr(ubx_of_match), | 
|         .pm        = &gnss_serial_pm_ops, | 
|     }, | 
|     .probe    = ubx_probe, | 
|     .remove    = ubx_remove, | 
| }; | 
| module_serdev_device_driver(ubx_driver); | 
|   | 
| MODULE_AUTHOR("Johan Hovold <johan@kernel.org>"); | 
| MODULE_DESCRIPTION("u-blox GNSS receiver driver"); | 
| MODULE_LICENSE("GPL v2"); |