blob: ec3b6b984f0857ce03602bf9be19da6f7a284914 [file] [log] [blame]
/*
* Google LWIS I/O Mapped Device Driver
*
* Copyright (c) 2018 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 "-ioreg-dev: " fmt
#include "lwis_device_ioreg.h"
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <uapi/linux/sched/types.h>
#include "lwis_init.h"
#include "lwis_interrupt.h"
#include "lwis_ioreg.h"
#include "lwis_periodic_io.h"
#include "lwis_util.h"
#ifdef CONFIG_OF
#include "lwis_dt.h"
#endif
#define LWIS_DRIVER_NAME "lwis-ioreg"
static int lwis_ioreg_device_enable(struct lwis_device *lwis_dev);
static int lwis_ioreg_device_disable(struct lwis_device *lwis_dev);
static int lwis_ioreg_register_io(struct lwis_device *lwis_dev, struct lwis_io_entry *entry,
int access_size);
static int lwis_ioreg_register_io_barrier(struct lwis_device *lwis_dev, bool read, bool write);
static struct lwis_device_subclass_operations ioreg_vops = {
.register_io = lwis_ioreg_register_io,
.register_io_barrier = lwis_ioreg_register_io_barrier,
.device_enable = lwis_ioreg_device_enable,
.device_disable = lwis_ioreg_device_disable,
.event_enable = NULL,
.event_flags_updated = NULL,
.close = NULL,
};
static struct lwis_event_subscribe_operations ioreg_subscribe_ops = {
.subscribe_event = NULL,
.unsubscribe_event = NULL,
.notify_event_subscriber = NULL,
.release = NULL,
};
static int lwis_ioreg_device_enable(struct lwis_device *lwis_dev)
{
return 0;
}
static int lwis_ioreg_device_disable(struct lwis_device *lwis_dev)
{
return 0;
}
static int lwis_ioreg_register_io(struct lwis_device *lwis_dev, struct lwis_io_entry *entry,
int access_size)
{
return lwis_ioreg_io_entry_rw((struct lwis_ioreg_device *)lwis_dev, entry, access_size);
}
static int lwis_ioreg_register_io_barrier(struct lwis_device *lwis_dev, bool use_read_barrier,
bool use_write_barrier)
{
return lwis_ioreg_set_io_barrier((struct lwis_ioreg_device *)lwis_dev, use_read_barrier,
use_write_barrier);
}
static int lwis_ioreg_device_setup(struct lwis_ioreg_device *ioreg_dev)
{
int ret = 0;
#ifdef CONFIG_OF
/* Parse device tree for device configurations */
ret = lwis_ioreg_device_parse_dt(ioreg_dev);
if (ret) {
dev_err(ioreg_dev->base_dev.dev, "Failed to parse device tree\n");
}
#else
/* Non-device-tree init: Save for future implementation */
ret = -ENOSYS;
#endif
return ret;
}
static int lwis_ioreg_device_probe(struct platform_device *plat_dev)
{
int ret = 0;
struct lwis_ioreg_device *ioreg_dev;
/* Allocate IOREG device specific data construct */
ioreg_dev = kzalloc(sizeof(struct lwis_ioreg_device), GFP_KERNEL);
if (!ioreg_dev) {
pr_err("Failed to allocate IOREG device structure\n");
return -ENOMEM;
}
ioreg_dev->base_dev.type = DEVICE_TYPE_IOREG;
ioreg_dev->base_dev.vops = ioreg_vops;
ioreg_dev->base_dev.subscribe_ops = ioreg_subscribe_ops;
/* Call the base device probe function */
ret = lwis_base_probe(&ioreg_dev->base_dev, plat_dev);
if (ret) {
pr_err("Error in lwis base probe\n");
goto error_probe;
}
/* Call IOREG device specific setup function */
ret = lwis_ioreg_device_setup(ioreg_dev);
if (ret) {
dev_err(ioreg_dev->base_dev.dev, "Error in IOREG device initialization\n");
lwis_base_unprobe(&ioreg_dev->base_dev);
goto error_probe;
}
/* Create associated kworker threads */
ret = lwis_create_kthread_workers(&ioreg_dev->base_dev, "lwis_ioreg_trans_kthread",
"lwis_ioreg_prd_io_kthread");
if (ret) {
dev_err(ioreg_dev->base_dev.dev, "Failed to create lwis_ioreg_kthread");
lwis_base_unprobe(&ioreg_dev->base_dev);
goto error_probe;
}
if (ioreg_dev->base_dev.transaction_thread_priority != 0) {
ret = lwis_set_kthread_priority(&ioreg_dev->base_dev,
ioreg_dev->base_dev.transaction_worker_thread,
ioreg_dev->base_dev.transaction_thread_priority);
if (ret) {
dev_err(ioreg_dev->base_dev.dev,
"Failed to set LWIS IOREG transaction kthread priority (%d)",
ret);
lwis_base_unprobe(&ioreg_dev->base_dev);
goto error_probe;
}
}
if (ioreg_dev->base_dev.periodic_io_thread_priority != 0) {
ret = lwis_set_kthread_priority(&ioreg_dev->base_dev,
ioreg_dev->base_dev.periodic_io_worker_thread,
ioreg_dev->base_dev.periodic_io_thread_priority);
if (ret) {
dev_err(ioreg_dev->base_dev.dev,
"Failed to set LWIS IOREG periodic io kthread priority (%d)",
ret);
lwis_base_unprobe(&ioreg_dev->base_dev);
goto error_probe;
}
}
dev_info(ioreg_dev->base_dev.dev, "IOREG Device Probe: Success\n");
return 0;
error_probe:
kfree(ioreg_dev);
return ret;
}
#ifdef CONFIG_PM
static int lwis_ioreg_device_suspend(struct device *dev)
{
struct lwis_device *lwis_dev = dev_get_drvdata(dev);
struct lwis_client *lwis_client, *n;
int ret = 0;
if (lwis_dev->enabled == 0) {
return ret;
}
/* Send an error event to userspace to handle the system suspend */
lwis_device_error_event_emit(lwis_dev, LWIS_ERROR_EVENT_ID_SYSTEM_SUSPEND,
/*payload=*/NULL, /*payload_size=*/0);
list_for_each_entry_safe (lwis_client, n, &lwis_dev->clients, node) {
if (!lwis_client->is_enabled) {
continue;
}
/* Clear event states for this client */
lwis_client_event_states_clear(lwis_client);
/* Flush all periodic io to complete */
ret = lwis_periodic_io_client_flush(lwis_client);
if (ret) {
dev_err(lwis_dev->dev,
"Failed to wait for in-process periodic io to complete\n");
}
/* Flush all pending transactions */
ret = lwis_transaction_client_flush(lwis_client);
if (ret) {
dev_err(lwis_dev->dev, "Failed to flush pending transactions\n");
}
/* Run cleanup transactions. */
lwis_transaction_client_cleanup(lwis_client);
lwis_client->is_enabled = false;
}
mutex_lock(&lwis_dev->client_lock);
ret = lwis_dev_power_down_locked(lwis_dev);
if (ret < 0) {
dev_err(lwis_dev->dev, "Failed to power down device\n");
}
lwis_device_event_states_clear_locked(lwis_dev);
lwis_dev->enabled = 0;
dev_warn(lwis_dev->dev, "Device disabled when system suspend\n");
mutex_unlock(&lwis_dev->client_lock);
return 0;
}
static int lwis_ioreg_device_resume(struct device *dev)
{
return 0;
}
static SIMPLE_DEV_PM_OPS(lwis_ioreg_device_ops, lwis_ioreg_device_suspend,
lwis_ioreg_device_resume);
#endif
#ifdef CONFIG_OF
static const struct of_device_id lwis_id_match[] = {
{ .compatible = LWIS_IOREG_DEVICE_COMPAT },
{},
};
// MODULE_DEVICE_TABLE(of, lwis_id_match);
static struct platform_driver lwis_driver = {
.probe = lwis_ioreg_device_probe,
.driver =
{
.name = LWIS_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = lwis_id_match,
.pm = &lwis_ioreg_device_ops,
},
};
#else /* CONFIG_OF not defined */
static struct platform_device_id lwis_driver_id[] = {
{
.name = LWIS_DRIVER_NAME,
.driver_data = 0,
},
{},
};
MODULE_DEVICE_TABLE(platform, lwis_driver_id);
static struct platform_driver lwis_driver = { .probe = lwis_ioreg_device_probe,
.id_table = lwis_driver_id,
.driver = {
.name = LWIS_DRIVER_NAME,
.owner = THIS_MODULE,
} };
#endif /* CONFIG_OF */
/*
* lwis_ioreg_device_init: Init function that will be called by the kernel
* initialization routines.
*/
int __init lwis_ioreg_device_init(void)
{
int ret = 0;
pr_info("IOREG device initialization\n");
ret = platform_driver_register(&lwis_driver);
if (ret) {
pr_err("platform_driver_register failed: %d\n", ret);
}
return ret;
}
int lwis_ioreg_device_deinit(void)
{
platform_driver_unregister(&lwis_driver);
return 0;
}