summaryrefslogtreecommitdiff
path: root/lwis_device_spi.c
diff options
context:
space:
mode:
authorHolmes Chou <holmeschou@google.com>2023-04-20 06:59:35 +0000
committerTreehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com>2023-05-19 00:38:00 +0000
commit745fc5fb029ffa1074e7674ca28e6d4a133ce506 (patch)
treeee4388232d51a4caae0d68c1139c504085f544d5 /lwis_device_spi.c
parente098b1c780598c0d50ec77c016641785551d7db5 (diff)
downloadlwis-745fc5fb029ffa1074e7674ca28e6d4a133ce506.tar.gz
LWIS: Add SPI device support
Add a new LWIS device type to support SPI devices Bug: 277152460 Test: SPI board probe Change-Id: I7c0bcdac8f7902a270a48c6862931a7ab747e306 Signed-off-by: Holmes Chou <holmeschou@google.com>
Diffstat (limited to 'lwis_device_spi.c')
-rw-r--r--lwis_device_spi.c260
1 files changed, 260 insertions, 0 deletions
diff --git a/lwis_device_spi.c b/lwis_device_spi.c
new file mode 100644
index 0000000..a4cdafa
--- /dev/null
+++ b/lwis_device_spi.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Google LWIS SPI Device Driver
+ *
+ * Copyright (c) 2023 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME "-spi-dev: " fmt
+
+#include "lwis_device_spi.h"
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/sched.h>
+#include <linux/sched/types.h>
+#include <linux/slab.h>
+#include <uapi/linux/sched/types.h>
+
+#include "lwis_device.h"
+#include "lwis_spi.h"
+#include "lwis_init.h"
+#include "lwis_util.h"
+#include "lwis_trace.h"
+
+#ifdef CONFIG_OF
+#include "lwis_dt.h"
+#endif
+
+#define LWIS_DRIVER_NAME "lwis-spi-device"
+
+static int lwis_spi_device_enable(struct lwis_device *lwis_dev);
+static int lwis_spi_device_disable(struct lwis_device *lwis_dev);
+static int lwis_spi_register_io(struct lwis_device *lwis_dev, struct lwis_io_entry *entry,
+ int access_size);
+
+static struct lwis_device_subclass_operations spi_vops = {
+ .register_io = lwis_spi_register_io,
+ .register_io_barrier = NULL,
+ .device_enable = lwis_spi_device_enable,
+ .device_disable = lwis_spi_device_disable,
+ .event_enable = NULL,
+ .event_flags_updated = NULL,
+ .close = NULL,
+};
+
+static struct lwis_event_subscribe_operations spi_subscribe_ops = {
+ .subscribe_event = NULL,
+ .unsubscribe_event = NULL,
+ .notify_event_subscriber = NULL,
+ .release = NULL,
+};
+
+static int lwis_spi_device_enable(struct lwis_device *lwis_dev)
+{
+ return 0;
+}
+
+static int lwis_spi_device_disable(struct lwis_device *lwis_dev)
+{
+ return 0;
+}
+
+static int lwis_spi_register_io(struct lwis_device *lwis_dev, struct lwis_io_entry *entry,
+ int access_size)
+{
+ struct lwis_spi_device *spi_dev;
+ spi_dev = container_of(lwis_dev, struct lwis_spi_device, base_dev);
+
+ /* Running in interrupt context is not supported as spi driver might sleep */
+ if (in_interrupt()) {
+ return -EAGAIN;
+ }
+ lwis_save_register_io_info(lwis_dev, entry, access_size);
+
+ return lwis_spi_io_entry_rw(spi_dev, entry);
+}
+
+static int lwis_spi_device_setup(struct lwis_spi_device *spi_dev)
+{
+ int ret;
+
+#ifdef CONFIG_OF
+ /* Parse device tree for device configurations */
+ ret = lwis_spi_device_parse_dt(spi_dev);
+ if (ret) {
+ dev_err(spi_dev->base_dev.dev, "Failed to parse device tree\n");
+ return ret;
+ }
+#else
+ /* Non-device-tree init: Save for future implementation */
+ return -ENOSYS;
+#endif
+
+ ret = spi_setup(spi_dev->spi);
+ if (ret) {
+ dev_err(spi_dev->base_dev.dev, "spi_setup fail (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lwis_spi_device_probe(struct spi_device *spi)
+{
+ int ret = 0;
+ struct lwis_spi_device *spi_dev;
+ struct device *dev = &spi->dev;
+
+ /* Allocate SPI device specific data construct */
+ spi_dev = devm_kzalloc(dev, sizeof(*spi_dev), GFP_KERNEL);
+ if (!spi_dev) {
+ dev_err(dev, "Failed to allocate spi device structure\n");
+ return -ENOMEM;
+ }
+
+ spi_dev->base_dev.type = DEVICE_TYPE_SPI;
+ spi_dev->base_dev.vops = spi_vops;
+ spi_dev->base_dev.subscribe_ops = spi_subscribe_ops;
+ spi_dev->base_dev.plat_dev = NULL;
+ spi_dev->base_dev.k_dev = &spi->dev;
+ spi_dev->spi = spi;
+
+ /* Initialize spin lock */
+ spin_lock_init(&spi_dev->spi_lock);
+
+ /* Call the base device probe function */
+ ret = lwis_base_probe(&spi_dev->base_dev);
+ if (ret) {
+ dev_err(dev, "Error in lwis base probe\n");
+ goto error_probe;
+ }
+ spi_set_drvdata(spi, &spi_dev->base_dev);
+
+ /* Call SPI device specific setup function */
+ ret = lwis_spi_device_setup(spi_dev);
+ if (ret) {
+ dev_err(spi_dev->base_dev.dev, "Error in spi device initialization\n");
+ lwis_base_unprobe(&spi_dev->base_dev);
+ goto error_probe;
+ }
+
+ /* Create associated kworker threads */
+ ret = lwis_create_kthread_workers(&spi_dev->base_dev);
+ if (ret) {
+ dev_err(spi_dev->base_dev.dev, "Failed to create lwis_spi_kthread");
+ lwis_base_unprobe(&spi_dev->base_dev);
+ goto error_probe;
+ }
+
+ if (spi_dev->base_dev.transaction_thread_priority != 0) {
+ ret = lwis_set_kthread_priority(&spi_dev->base_dev,
+ spi_dev->base_dev.transaction_worker_thread,
+ spi_dev->base_dev.transaction_thread_priority);
+ if (ret) {
+ dev_err(spi_dev->base_dev.dev,
+ "Failed to set LWIS SPI transaction kthread priority (%d)", ret);
+ lwis_base_unprobe(&spi_dev->base_dev);
+ goto error_probe;
+ }
+ }
+
+ dev_info(spi_dev->base_dev.dev, "SPI Device Probe: Success\n");
+
+ return 0;
+
+error_probe:
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int lwis_spi_device_suspend(struct device *dev)
+{
+ struct lwis_device *lwis_dev = dev_get_drvdata(dev);
+
+ if (lwis_dev->pm_hibernation == 0) {
+ return 0;
+ }
+
+ if (lwis_dev->enabled != 0) {
+ dev_warn(lwis_dev->dev, "Can't suspend because %s is in use!\n", lwis_dev->name);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int lwis_spi_device_resume(struct device *dev)
+{
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(lwis_spi_device_ops, lwis_spi_device_suspend, lwis_spi_device_resume);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id lwis_id_match[] = {
+ { .compatible = LWIS_SPI_DEVICE_COMPAT },
+ {},
+};
+// MODULE_DEVICE_TABLE(of, lwis_id_match);
+
+static struct spi_driver lwis_driver = {
+ .probe = lwis_spi_device_probe,
+ .driver = {
+ .name = LWIS_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = lwis_id_match,
+ .pm = &lwis_spi_device_ops,
+ },
+};
+#else /* CONFIG_OF not defined */
+static struct spi_device_id lwis_driver_id[] = {
+ {
+ .name = LWIS_DRIVER_NAME,
+ .driver_data = 0,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, lwis_driver_id);
+
+static struct spi_driver lwis_driver = { .probe = lwis_spi_device_probe,
+ .id_table = lwis_driver_id,
+ .driver = {
+ .name = LWIS_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ } };
+#endif /* CONFIG_OF */
+
+/*
+ * lwis_spi_device_init: Init function that will be called by the kernel
+ * initialization routines.
+ */
+int __init lwis_spi_device_init(void)
+{
+ int ret = 0;
+
+ pr_info("SPI device initialization\n");
+
+ ret = spi_register_driver(&lwis_driver);
+ if (ret) {
+ pr_err("spi_register_driver failed: %d\n", ret);
+ }
+
+ return ret;
+}
+
+int lwis_spi_device_deinit(void)
+{
+ spi_unregister_driver(&lwis_driver);
+ return 0;
+}