forked from mirrors/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>
This commit is contained in:
		
							parent
							
								
									98ddec80fd
								
							
						
					
					
						commit
						37768b054f
					
				
					 4 changed files with 332 additions and 0 deletions
				
			
		| 
						 | 
					@ -9,3 +9,10 @@ menuconfig GNSS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	  To compile this driver as a module, choose M here: the module will
 | 
						  To compile this driver as a module, choose M here: the module will
 | 
				
			||||||
	  be called gnss.
 | 
						  be called gnss.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if GNSS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config GNSS_SERIAL
 | 
				
			||||||
 | 
						tristate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					endif # GNSS
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,3 +5,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
obj-$(CONFIG_GNSS)			+= gnss.o
 | 
					obj-$(CONFIG_GNSS)			+= gnss.o
 | 
				
			||||||
gnss-y := core.o
 | 
					gnss-y := core.o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					obj-$(CONFIG_GNSS_SERIAL)		+= gnss-serial.o
 | 
				
			||||||
 | 
					gnss-serial-y := serial.o
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										275
									
								
								drivers/gnss/serial.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								drivers/gnss/serial.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,275 @@
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: GPL-2.0
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Generic serial 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/pm.h>
 | 
				
			||||||
 | 
					#include <linux/pm_runtime.h>
 | 
				
			||||||
 | 
					#include <linux/serdev.h>
 | 
				
			||||||
 | 
					#include <linux/slab.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "serial.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int gnss_serial_open(struct gnss_device *gdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial = gnss_get_drvdata(gdev);
 | 
				
			||||||
 | 
						struct serdev_device *serdev = gserial->serdev;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = serdev_device_open(serdev);
 | 
				
			||||||
 | 
						if (ret)
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						serdev_device_set_baudrate(serdev, gserial->speed);
 | 
				
			||||||
 | 
						serdev_device_set_flow_control(serdev, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = pm_runtime_get_sync(&serdev->dev);
 | 
				
			||||||
 | 
						if (ret < 0) {
 | 
				
			||||||
 | 
							pm_runtime_put_noidle(&serdev->dev);
 | 
				
			||||||
 | 
							goto err_close;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_close:
 | 
				
			||||||
 | 
						serdev_device_close(serdev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void gnss_serial_close(struct gnss_device *gdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial = gnss_get_drvdata(gdev);
 | 
				
			||||||
 | 
						struct serdev_device *serdev = gserial->serdev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						serdev_device_close(serdev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pm_runtime_put(&serdev->dev);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int gnss_serial_write_raw(struct gnss_device *gdev,
 | 
				
			||||||
 | 
							const unsigned char *buf, size_t count)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial = gnss_get_drvdata(gdev);
 | 
				
			||||||
 | 
						struct serdev_device *serdev = gserial->serdev;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* write is only buffered synchronously */
 | 
				
			||||||
 | 
						ret = serdev_device_write(serdev, buf, count, 0);
 | 
				
			||||||
 | 
						if (ret < 0)
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* FIXME: determine if interrupted? */
 | 
				
			||||||
 | 
						serdev_device_wait_until_sent(serdev, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return count;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct gnss_operations gnss_serial_gnss_ops = {
 | 
				
			||||||
 | 
						.open		= gnss_serial_open,
 | 
				
			||||||
 | 
						.close		= gnss_serial_close,
 | 
				
			||||||
 | 
						.write_raw	= gnss_serial_write_raw,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int gnss_serial_receive_buf(struct serdev_device *serdev,
 | 
				
			||||||
 | 
										const unsigned char *buf, size_t count)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial = serdev_device_get_drvdata(serdev);
 | 
				
			||||||
 | 
						struct gnss_device *gdev = gserial->gdev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return gnss_insert_raw(gdev, buf, count);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct serdev_device_ops gnss_serial_serdev_ops = {
 | 
				
			||||||
 | 
						.receive_buf	= gnss_serial_receive_buf,
 | 
				
			||||||
 | 
						.write_wakeup	= serdev_device_write_wakeup,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int gnss_serial_set_power(struct gnss_serial *gserial,
 | 
				
			||||||
 | 
										enum gnss_serial_pm_state state)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!gserial->ops || !gserial->ops->set_power)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return gserial->ops->set_power(gserial, state);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * FIXME: need to provide subdriver defaults or separate dt parsing from
 | 
				
			||||||
 | 
					 * allocation.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static int gnss_serial_parse_dt(struct serdev_device *serdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial = serdev_device_get_drvdata(serdev);
 | 
				
			||||||
 | 
						struct device_node *node = serdev->dev.of_node;
 | 
				
			||||||
 | 
						u32 speed = 4800;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						of_property_read_u32(node, "current-speed", &speed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gserial->speed = speed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct gnss_serial *gnss_serial_allocate(struct serdev_device *serdev,
 | 
				
			||||||
 | 
											size_t data_size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial;
 | 
				
			||||||
 | 
						struct gnss_device *gdev;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gserial = kzalloc(sizeof(*gserial) + data_size, GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!gserial)
 | 
				
			||||||
 | 
							return ERR_PTR(-ENOMEM);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gdev = gnss_allocate_device(&serdev->dev);
 | 
				
			||||||
 | 
						if (!gdev) {
 | 
				
			||||||
 | 
							ret = -ENOMEM;
 | 
				
			||||||
 | 
							goto err_free_gserial;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gdev->ops = &gnss_serial_gnss_ops;
 | 
				
			||||||
 | 
						gnss_set_drvdata(gdev, gserial);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gserial->serdev = serdev;
 | 
				
			||||||
 | 
						gserial->gdev = gdev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						serdev_device_set_drvdata(serdev, gserial);
 | 
				
			||||||
 | 
						serdev_device_set_client_ops(serdev, &gnss_serial_serdev_ops);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = gnss_serial_parse_dt(serdev);
 | 
				
			||||||
 | 
						if (ret)
 | 
				
			||||||
 | 
							goto err_put_device;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return gserial;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_put_device:
 | 
				
			||||||
 | 
						gnss_put_device(gserial->gdev);
 | 
				
			||||||
 | 
					err_free_gserial:
 | 
				
			||||||
 | 
						kfree(gserial);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ERR_PTR(ret);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(gnss_serial_allocate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void gnss_serial_free(struct gnss_serial *gserial)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						gnss_put_device(gserial->gdev);
 | 
				
			||||||
 | 
						kfree(gserial);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(gnss_serial_free);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int gnss_serial_register(struct gnss_serial *gserial)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct serdev_device *serdev = gserial->serdev;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (IS_ENABLED(CONFIG_PM)) {
 | 
				
			||||||
 | 
							pm_runtime_enable(&serdev->dev);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ret = gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE);
 | 
				
			||||||
 | 
							if (ret < 0)
 | 
				
			||||||
 | 
								return ret;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = gnss_register_device(gserial->gdev);
 | 
				
			||||||
 | 
						if (ret)
 | 
				
			||||||
 | 
							goto err_disable_rpm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_disable_rpm:
 | 
				
			||||||
 | 
						if (IS_ENABLED(CONFIG_PM))
 | 
				
			||||||
 | 
							pm_runtime_disable(&serdev->dev);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							gnss_serial_set_power(gserial, GNSS_SERIAL_OFF);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(gnss_serial_register);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void gnss_serial_deregister(struct gnss_serial *gserial)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct serdev_device *serdev = gserial->serdev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gnss_deregister_device(gserial->gdev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (IS_ENABLED(CONFIG_PM))
 | 
				
			||||||
 | 
							pm_runtime_disable(&serdev->dev);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							gnss_serial_set_power(gserial, GNSS_SERIAL_OFF);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(gnss_serial_deregister);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_PM
 | 
				
			||||||
 | 
					static int gnss_serial_runtime_suspend(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return gnss_serial_set_power(gserial, GNSS_SERIAL_STANDBY);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int gnss_serial_runtime_resume(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif /* CONFIG_PM */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int gnss_serial_prepare(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (pm_runtime_suspended(dev))
 | 
				
			||||||
 | 
							return 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_PM_SLEEP
 | 
				
			||||||
 | 
					static int gnss_serial_suspend(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						int ret = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * FIXME: serdev currently lacks support for managing the underlying
 | 
				
			||||||
 | 
						 * device's wakeup settings. A workaround would be to close the serdev
 | 
				
			||||||
 | 
						 * device here if it is open.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!pm_runtime_suspended(dev))
 | 
				
			||||||
 | 
							ret = gnss_serial_set_power(gserial, GNSS_SERIAL_STANDBY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int gnss_serial_resume(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct gnss_serial *gserial = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						int ret = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!pm_runtime_suspended(dev))
 | 
				
			||||||
 | 
							ret = gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif /* CONFIG_PM_SLEEP */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const struct dev_pm_ops gnss_serial_pm_ops = {
 | 
				
			||||||
 | 
						.prepare	= gnss_serial_prepare,
 | 
				
			||||||
 | 
						SET_SYSTEM_SLEEP_PM_OPS(gnss_serial_suspend, gnss_serial_resume)
 | 
				
			||||||
 | 
						SET_RUNTIME_PM_OPS(gnss_serial_runtime_suspend, gnss_serial_runtime_resume, NULL)
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(gnss_serial_pm_ops);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
 | 
				
			||||||
 | 
					MODULE_DESCRIPTION("Generic serial GNSS receiver driver");
 | 
				
			||||||
 | 
					MODULE_LICENSE("GPL v2");
 | 
				
			||||||
							
								
								
									
										47
									
								
								drivers/gnss/serial.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								drivers/gnss/serial.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					/* SPDX-License-Identifier: GPL-2.0 */
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Generic serial GNSS receiver driver
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (C) 2018 Johan Hovold <johan@kernel.org>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef _LINUX_GNSS_SERIAL_H
 | 
				
			||||||
 | 
					#define _LINUX_GNSS_SERIAL_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <asm/termbits.h>
 | 
				
			||||||
 | 
					#include <linux/pm.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct gnss_serial {
 | 
				
			||||||
 | 
						struct serdev_device *serdev;
 | 
				
			||||||
 | 
						struct gnss_device *gdev;
 | 
				
			||||||
 | 
						speed_t	speed;
 | 
				
			||||||
 | 
						const struct gnss_serial_ops *ops;
 | 
				
			||||||
 | 
						unsigned long drvdata[0];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum gnss_serial_pm_state {
 | 
				
			||||||
 | 
						GNSS_SERIAL_OFF,
 | 
				
			||||||
 | 
						GNSS_SERIAL_ACTIVE,
 | 
				
			||||||
 | 
						GNSS_SERIAL_STANDBY,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct gnss_serial_ops {
 | 
				
			||||||
 | 
						int (*set_power)(struct gnss_serial *gserial,
 | 
				
			||||||
 | 
									enum gnss_serial_pm_state state);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const struct dev_pm_ops gnss_serial_pm_ops;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct gnss_serial *gnss_serial_allocate(struct serdev_device *gserial,
 | 
				
			||||||
 | 
											size_t data_size);
 | 
				
			||||||
 | 
					void gnss_serial_free(struct gnss_serial *gserial);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int gnss_serial_register(struct gnss_serial *gserial);
 | 
				
			||||||
 | 
					void gnss_serial_deregister(struct gnss_serial *gserial);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline void *gnss_serial_get_drvdata(struct gnss_serial *gserial)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return gserial->drvdata;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* _LINUX_GNSS_SERIAL_H */
 | 
				
			||||||
		Loading…
	
		Reference in a new issue