blob: f1b58fee2a69d48f86c3a809827c49cb164f7333 [file] [log] [blame]
/*
* Google LWIS SLC 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.
*/
#include "lwis_device_slc.h"
#include "lwis_init.h"
#include <linux/anon_inodes.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/file.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <soc/google/pt.h>
#ifdef CONFIG_OF
#include "lwis_dt.h"
#endif
#define LWIS_DRIVER_NAME "lwis-slc"
#define SIZE_TO_KB(x) x / 1024
static const struct file_operations pt_file_ops = {
.owner = THIS_MODULE,
};
static int lwis_slc_enable(struct lwis_device *lwis_dev);
static int lwis_slc_disable(struct lwis_device *lwis_dev);
static struct lwis_device_subclass_operations slc_vops = {
.register_io = NULL,
.register_io_barrier = NULL,
.device_enable = lwis_slc_enable,
.device_disable = lwis_slc_disable,
.event_enable = NULL,
.event_flags_updated = NULL,
.close = NULL,
};
static struct lwis_event_subscribe_operations slc_subscribe_ops = {
.subscribe_event = NULL,
.unsubscribe_event = NULL,
.notify_event_subscriber = NULL,
.release = NULL,
};
static int lwis_slc_enable(struct lwis_device *lwis_dev)
{
#ifdef CONFIG_OF
struct device_node *node = lwis_dev->plat_dev->dev.of_node;
int num_pt_id = 0, num_pt_size = 0, i = 0, ret = 0;
struct lwis_slc_device *slc_dev = (struct lwis_slc_device *)lwis_dev;
size_t pt_size_kb[MAX_NUM_PT] = {};
if (!lwis_dev) {
pr_err("LWIS device cannot be NULL\n");
return -ENODEV;
}
num_pt_id = of_property_count_strings(node, "pt_id");
num_pt_size = of_property_count_u32_elems(node, "pt_size");
if (num_pt_id != num_pt_size) {
dev_err(lwis_dev->dev,
"Mismatch partition names and sizes: %d partition names VS %d partition sizes",
num_pt_id, num_pt_size);
return -EINVAL;
}
if (num_pt_id > MAX_NUM_PT) {
dev_err(lwis_dev->dev,
"The number of partitions in slc device is %d, exceeds the max value %d",
num_pt_id, MAX_NUM_PT);
return -EINVAL;
}
for (i = 0; i < num_pt_id; i++) {
/* Make sure pt_size are in ascending order, since it's required by the allocation logic. */
of_property_read_u32_index(node, "pt_size", i, (u32 *)&pt_size_kb[i]);
if (i > 0 && pt_size_kb[i] < pt_size_kb[i - 1]) {
dev_err(lwis_dev->dev, "SLC partition sizes are not in ascending order!");
return -EINVAL;
}
}
/* Initialize SLC partitions and get a handle */
slc_dev->partition_handle = pt_client_register(node, NULL, NULL);
if (IS_ERR(slc_dev->partition_handle)) {
ret = PTR_ERR(slc_dev->partition_handle);
dev_err(lwis_dev->dev, "Failed to register PT client (%d)\n", ret);
slc_dev->partition_handle = NULL;
return ret;
}
slc_dev->num_pt = num_pt_id;
for (i = 0; i < slc_dev->num_pt; i++) {
slc_dev->pt[i].id = i;
slc_dev->pt[i].size_kb = pt_size_kb[i];
slc_dev->pt[i].fd = -1;
slc_dev->pt[i].partition_id = PT_PTID_INVALID;
slc_dev->pt[i].partition_handle = slc_dev->partition_handle;
}
return 0;
#else /* CONFIG_OF not defined */
return -ENOENT;
#endif /* CONFIG_OF */
}
static int lwis_slc_disable(struct lwis_device *lwis_dev)
{
struct lwis_slc_device *slc_dev = (struct lwis_slc_device *)lwis_dev;
int i = 0;
if (!lwis_dev) {
pr_err("LWIS device cannot be NULL\n");
return -ENODEV;
}
if (!slc_dev->partition_handle) {
dev_err(slc_dev->base_dev.dev, "Partition handle is NULL\n");
return -ENODEV;
}
for (i = 0; i < slc_dev->num_pt; i++) {
if (slc_dev->pt[i].partition_id != PT_PTID_INVALID) {
dev_info(slc_dev->base_dev.dev,
"Closing partition id %d at device shutdown", slc_dev->pt[i].id);
pt_client_disable(slc_dev->partition_handle, slc_dev->pt[i].id);
slc_dev->pt[i].partition_id = PT_PTID_INVALID;
slc_dev->pt[i].fd = -1;
}
}
pt_client_unregister(slc_dev->partition_handle);
return 0;
}
int lwis_slc_buffer_alloc(struct lwis_device *lwis_dev, struct lwis_alloc_buffer_info *alloc_info)
{
struct lwis_slc_device *slc_dev = (struct lwis_slc_device *)lwis_dev;
int i = 0, fd_or_err = -1;
ptid_t partition_id = PT_PTID_INVALID;
if (!lwis_dev) {
pr_err("LWIS device cannot be NULL\n");
return -ENODEV;
}
if (!alloc_info) {
dev_err(slc_dev->base_dev.dev, "Buffer alloc info is NULL\n");
return -EINVAL;
}
if (!slc_dev->partition_handle) {
dev_err(slc_dev->base_dev.dev, "Partition handle is NULL\n");
return -ENODEV;
}
if (slc_dev->num_pt <= 0) {
dev_err(slc_dev->base_dev.dev, "No valid partitions is found in SLC\n");
return -EINVAL;
}
for (i = 0; i < slc_dev->num_pt; i++) {
if (slc_dev->pt[i].partition_id == PT_PTID_INVALID &&
slc_dev->pt[i].size_kb >= SIZE_TO_KB(alloc_info->size)) {
partition_id =
pt_client_enable(slc_dev->partition_handle, slc_dev->pt[i].id);
if (partition_id != PT_PTID_INVALID) {
fd_or_err = anon_inode_getfd("slc_pt_file", &pt_file_ops,
&slc_dev->pt[i], O_CLOEXEC);
if (fd_or_err < 0) {
dev_err(lwis_dev->dev,
"Failed to create a new file instance for the partition\n");
return fd_or_err;
}
slc_dev->pt[i].fd = fd_or_err;
slc_dev->pt[i].partition_id = partition_id;
alloc_info->dma_fd = fd_or_err;
alloc_info->partition_id = slc_dev->pt[i].partition_id;
return 0;
} else {
dev_err(lwis_dev->dev, "Failed to enable partition id %d\n",
slc_dev->pt[i].id);
return -EPROTO;
}
}
}
dev_err(lwis_dev->dev,
"Failed to find valid partition, largest size supported is %zuKB, asking for %zuKB\n",
slc_dev->pt[slc_dev->num_pt - 1].size_kb, SIZE_TO_KB(alloc_info->size));
for (i = 0; i < slc_dev->num_pt; i++) {
dev_err(lwis_dev->dev, "Partition[%d]: size %zuKB is %s\n", i,
slc_dev->pt[i].size_kb,
(slc_dev->pt[i].partition_id == PT_PTID_INVALID) ? "NOT in use" : "in use");
}
return -EINVAL;
}
int lwis_slc_buffer_free(struct lwis_device *lwis_dev, int fd)
{
struct file *fp;
struct slc_partition *slc_pt;
if (!lwis_dev) {
pr_err("LWIS device cannot be NULL\n");
return -ENODEV;
}
fp = fget(fd);
if (fp == NULL) {
return -EBADF;
}
slc_pt = fp->private_data;
if (slc_pt->fd != fd) {
dev_warn(lwis_dev->dev, "Stale SLC buffer free for fd %d with ptid %d\n", fd,
slc_pt->partition_id);
return -EINVAL;
}
if (slc_pt->partition_id != PT_PTID_INVALID && slc_pt->partition_handle) {
pt_client_disable(slc_pt->partition_handle, slc_pt->id);
slc_pt->partition_id = PT_PTID_INVALID;
slc_pt->fd = -1;
}
fput(fp);
return 0;
}
static int lwis_slc_device_probe(struct platform_device *plat_dev)
{
int ret = 0;
struct lwis_slc_device *slc_dev;
/* Allocate SLC device specific data construct */
slc_dev = kzalloc(sizeof(struct lwis_slc_device), GFP_KERNEL);
if (!slc_dev) {
pr_err("Failed to allocate slc device structure\n");
return -ENOMEM;
}
slc_dev->base_dev.type = DEVICE_TYPE_SLC;
slc_dev->base_dev.vops = slc_vops;
slc_dev->base_dev.subscribe_ops = slc_subscribe_ops;
/* Call the base device probe function */
ret = lwis_base_probe((struct lwis_device *)slc_dev, plat_dev);
if (ret) {
pr_err("Error in lwis base probe\n");
goto error_probe;
}
dev_info(slc_dev->base_dev.dev, "SLC Device Probe: Success\n");
return 0;
error_probe:
kfree(slc_dev);
return ret;
}
#ifdef CONFIG_OF
static const struct of_device_id lwis_id_match[] = {
{ .compatible = LWIS_SLC_DEVICE_COMPAT },
{},
};
// MODULE_DEVICE_TABLE(of, lwis_id_match);
static struct platform_driver lwis_driver = {
.probe = lwis_slc_device_probe,
.driver = {
.name = LWIS_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = lwis_id_match,
},
};
#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_slc_device_probe,
.id_table = lwis_driver_id,
.driver = {
.name = LWIS_DRIVER_NAME,
.owner = THIS_MODULE,
} };
#endif /* CONFIG_OF */
/*
* lwis_slc_device_init: Init function that will be called by the kernel
* initialization routines.
*/
int __init lwis_slc_device_init(void)
{
int ret = 0;
pr_info("SLC device initialization\n");
ret = platform_driver_register(&lwis_driver);
if (ret) {
pr_err("platform_driver_register failed: %d\n", ret);
}
return ret;
}
int lwis_slc_device_deinit(void)
{
platform_driver_unregister(&lwis_driver);
return 0;
}