| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * Test driver for GNSS. This driver requires the serdev binding and protocol | 
 |  * type to be specified on the module command line. | 
 |  * | 
 |  * Copyright 2019 Google LLC | 
 |  */ | 
 |  | 
 | #include <linux/device.h> | 
 | #include <linux/gnss.h> | 
 | #include <linux/module.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/serdev.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/string.h> | 
 |  | 
 | #include "serial.h" | 
 |  | 
 | #define GNSS_CMDLINE_MODULE_NAME "gnss-cmdline" | 
 |  | 
 | #define gnss_cmdline_err(...) \ | 
 | 	pr_err(GNSS_CMDLINE_MODULE_NAME ": " __VA_ARGS__) | 
 |  | 
 | static char *serdev; | 
 | module_param(serdev, charp, 0644); | 
 | MODULE_PARM_DESC(serdev, "serial device to wrap"); | 
 |  | 
 | static int type; | 
 | module_param(type, int, 0644); | 
 | MODULE_PARM_DESC(serdev, "GNSS protocol type (see 'enum gnss_type')"); | 
 |  | 
 | static struct serdev_device *serdev_device; | 
 |  | 
 | static int name_match(struct device *dev, void *data) | 
 | { | 
 | 	return strstr(dev_name(dev), data) != NULL; | 
 | } | 
 |  | 
 | static int __init gnss_cmdline_init(void) | 
 | { | 
 | 	struct device *serial_dev, *port_dev, *serdev_dev; | 
 | 	char *driver_name, *port_name, *serdev_name; | 
 | 	char *serdev_dup, *serdev_dup_sep; | 
 | 	struct gnss_serial *gserial; | 
 | 	int err = -ENODEV; | 
 |  | 
 | 	/* User did not set the serdev module parameter */ | 
 | 	if (!serdev) | 
 | 		return 0; | 
 |  | 
 | 	if (type < 0 || type >= GNSS_TYPE_COUNT) { | 
 | 		gnss_cmdline_err("invalid gnss type '%d'\n", type); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	serdev_dup = serdev_dup_sep = kstrdup(serdev, GFP_KERNEL); | 
 | 	if (!serdev_dup) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	driver_name = strsep(&serdev_dup_sep, "/"); | 
 | 	if (!driver_name) { | 
 | 		gnss_cmdline_err("driver name missing\n"); | 
 | 		goto err_free_serdev_dup; | 
 | 	} | 
 |  | 
 | 	port_name = strsep(&serdev_dup_sep, "/"); | 
 | 	if (!port_name) { | 
 | 		gnss_cmdline_err("port name missing\n"); | 
 | 		goto err_free_serdev_dup; | 
 | 	} | 
 |  | 
 | 	serdev_name = strsep(&serdev_dup_sep, "/"); | 
 | 	if (!serdev_name) { | 
 | 		gnss_cmdline_err("serdev name missing\n"); | 
 | 		goto err_free_serdev_dup; | 
 | 	} | 
 |  | 
 | 	/* Find the driver device instance (e.g. serial8250) */ | 
 | 	serial_dev = bus_find_device_by_name(&platform_bus_type, | 
 | 					     NULL, driver_name); | 
 | 	if (!serial_dev) { | 
 | 		gnss_cmdline_err("no device '%s'\n", driver_name); | 
 | 		goto err_free_serdev_dup; | 
 | 	} | 
 |  | 
 | 	/* Find the port device instance (e.g. serial0) */ | 
 | 	port_dev = device_find_child(serial_dev, port_name, name_match); | 
 | 	if (!port_dev) { | 
 | 		gnss_cmdline_err("no port '%s'\n", port_name); | 
 | 		goto err_free_serdev_dup; | 
 | 	} | 
 |  | 
 | 	/* Find the serdev device instance (e.g. serial0-0) */ | 
 | 	serdev_dev = device_find_child(port_dev, serdev_name, name_match); | 
 | 	if (!serdev_dev) { | 
 | 		gnss_cmdline_err("no serdev '%s'\n", serdev_name); | 
 | 		goto err_free_serdev_dup; | 
 | 	} | 
 |  | 
 | 	gserial = gnss_serial_allocate(to_serdev_device(serdev_dev), 0); | 
 | 	if (IS_ERR(gserial)) { | 
 | 		err = PTR_ERR(gserial); | 
 | 		goto err_free_serdev_dup; | 
 | 	} | 
 |  | 
 | 	gserial->gdev->type = type; | 
 |  | 
 | 	err = gnss_serial_register(gserial); | 
 | 	if (err) { | 
 | 		gnss_serial_free(gserial); | 
 | 		goto err_free_serdev_dup; | 
 | 	} | 
 |  | 
 | 	serdev_device = to_serdev_device(serdev_dev); | 
 | 	err = 0; | 
 | err_free_serdev_dup: | 
 | 	kfree(serdev_dup); | 
 | 	return err; | 
 | } | 
 |  | 
 | static void __exit gnss_cmdline_exit(void) | 
 | { | 
 | 	struct gnss_serial *gserial; | 
 |  | 
 | 	if (!serdev_device) | 
 | 		return; | 
 |  | 
 | 	gserial = serdev_device_get_drvdata(serdev_device); | 
 |  | 
 | 	gnss_serial_deregister(gserial); | 
 | 	gnss_serial_free(gserial); | 
 | } | 
 |  | 
 | module_init(gnss_cmdline_init); | 
 | module_exit(gnss_cmdline_exit); | 
 |  | 
 | MODULE_AUTHOR("Alistair Delva <adelva@google.com>"); | 
 | MODULE_DESCRIPTION("GNSS command line driver"); | 
 | MODULE_LICENSE("GPL v2"); |