blob: 53db3496fdec66041b59fc6ac8841b3cd5807249 [file] [log] [blame] [edit]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/mailbox_client.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/list.h>
#include <linux/amlogic/aml_mbox.h>
#include <dt-bindings/mailbox/amlogic,mbox.h>
#include "meson_mbox_devfs.h"
#define DRIVER_NAME "mbox-devfs"
struct aml_mbox_dev {
struct list_head list;
dev_t dev_t;
struct cdev cdev;
struct device *dev;
struct device *p_dev;
struct mbox_chan *mbox_chan;
const char *name;
u32 dest;
};
struct aml_mbox_priv_data {
u32 cmd;
char data[MBOX_USER_SIZE];
} __packed;
struct aml_mbox_priv {
struct aml_mbox_dev *aml_dev;
struct aml_mbox_data *aml_data;
};
static ssize_t mbox_message_write(struct file *filp,
const char __user *userbuf,
size_t count, loff_t *ppos)
{
struct aml_mbox_priv *aml_priv = filp->private_data;
struct aml_mbox_dev *aml_dev = aml_priv->aml_dev;
struct mbox_chan *mbox_chan = aml_dev->mbox_chan;
struct aml_mbox_chan *aml_chan = mbox_chan->con_priv;
struct aml_mbox_data *aml_data = aml_priv->aml_data;
struct aml_mbox_priv_data aml_priv_data;
struct device *dev = aml_dev->p_dev;
int ret = 0;
if (count > MBOX_USER_SIZE) {
dev_err(dev, "Msg len %zd over range dest %d\n", count, aml_dev->dest);
return -EINVAL;
}
ret = copy_from_user(&aml_priv_data, userbuf, count);
if (ret)
return ret;
aml_data->txbuf = aml_priv_data.data;
aml_data->cmd = aml_priv_data.cmd;
aml_data->txsize = count - sizeof(u32);
switch (aml_dev->dest) {
case MAILBOX_AOCPU:
aml_data->rxsize = count - sizeof(u32);
aml_data->sync = MBOX_SYNC;
break;
case MAILBOX_DSP:
aml_data->rxsize = 0;
aml_data->sync = MBOX_TSYNC;
break;
case MAILBOX_SECPU:
aml_data->txbuf = &aml_priv_data;
aml_data->rxsize = 0;
aml_data->txsize = count;
aml_data->sync = MBOX_SYNC;
break;
default:
break;
};
mutex_lock(&aml_chan->mutex);
ret = mbox_send_message(mbox_chan, aml_data);
if (ret < 0) {
dev_err(dev, "Msg send fail %d dest %d\n", ret, aml_dev->dest);
complete(&aml_data->complete);
mutex_unlock(&aml_chan->mutex);
return ret;
}
if (aml_data->sync == MBOX_SYNC)
complete(&aml_data->complete);
mutex_unlock(&aml_chan->mutex);
return count;
}
static ssize_t mbox_message_read(struct file *filp, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct aml_mbox_priv *aml_priv = filp->private_data;
struct aml_mbox_dev *aml_dev = aml_priv->aml_dev;
struct aml_mbox_data *aml_data = aml_priv->aml_data;
struct device *dev = aml_dev->p_dev;
int ret = 0;
int rxsize;
ret = wait_for_completion_killable(&aml_data->complete);
if (ret < 0) {
dev_err(dev, "Read msg wait killed %d\n", ret);
return -ENXIO;
}
barrier();
*ppos = 0;
if (count > MBOX_USER_SIZE)
count = MBOX_USER_SIZE;
rxsize = count > aml_data->rxsize ? aml_data->rxsize : count;
ret = simple_read_from_buffer(userbuf, rxsize, ppos,
aml_data->rxbuf, MBOX_USER_SIZE);
return ret;
}
static long mbox_message_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
static int mbox_message_open(struct inode *inode, struct file *filp)
{
struct cdev *cdev = inode->i_cdev;
struct aml_mbox_dev *aml_dev = container_of(cdev, struct aml_mbox_dev, cdev);
struct aml_mbox_priv *aml_priv;
struct aml_mbox_data *aml_data;
aml_priv = devm_kzalloc(aml_dev->p_dev, sizeof(*aml_priv), GFP_KERNEL);
if (IS_ERR(aml_priv))
return -ENOMEM;
aml_data = devm_kzalloc(aml_dev->p_dev, sizeof(*aml_data), GFP_KERNEL);
if (IS_ERR(aml_data)) {
devm_kfree(aml_dev->p_dev, aml_priv);
return -ENOMEM;
}
switch (aml_dev->dest) {
case MAILBOX_AOCPU:
case MAILBOX_DSP:
case MAILBOX_SECPU:
aml_data->rxbuf = devm_kzalloc(aml_dev->p_dev, MBOX_USER_SIZE, GFP_KERNEL);
if (IS_ERR(aml_data->rxbuf)) {
devm_kfree(aml_dev->p_dev, aml_priv);
devm_kfree(aml_dev->p_dev, aml_data);
return -ENOMEM;
}
break;
default:
break;
};
init_completion(&aml_data->complete);
aml_priv->aml_dev = aml_dev;
aml_priv->aml_data = aml_data;
filp->private_data = aml_priv;
return 0;
}
static int mbox_message_release(struct inode *inode, struct file *filp)
{
struct aml_mbox_priv *aml_priv = filp->private_data;
struct aml_mbox_dev *aml_dev = aml_priv->aml_dev;
switch (aml_dev->dest) {
case MAILBOX_AOCPU:
case MAILBOX_DSP:
devm_kfree(aml_dev->p_dev, aml_priv->aml_data->rxbuf);
break;
default:
break;
};
devm_kfree(aml_dev->p_dev, aml_priv->aml_data);
devm_kfree(aml_dev->p_dev, aml_priv);
return 0;
}
static const struct file_operations mbox_message_ops = {
.write = mbox_message_write,
.read = mbox_message_read,
.open = mbox_message_open,
.release = mbox_message_release,
.unlocked_ioctl = mbox_message_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = mbox_message_ioctl,
#endif
};
static int mbox_cdev_init(struct device *dev)
{
static struct class *mbox_class;
struct aml_mbox_dev *mbox_dev;
dev_t dev_t;
u32 idx;
int err = 0;
int mbox_nums = 0;
dev_dbg(dev, "mbox devfs init start\n");
err = of_property_read_u32(dev->of_node,
"mbox-nums", &mbox_nums);
if (err < 0) {
dev_err(dev, "No mbox-nums\n");
return -1;
}
mbox_class = class_create(THIS_MODULE, "mbox_devfs");
if (IS_ERR(mbox_class))
goto err;
err = alloc_chrdev_region(&dev_t, 0, mbox_nums, DRIVER_NAME);
if (err < 0) {
dev_err(dev, "alloc dev_t failed\n");
err = -1;
goto class_err;
}
for (idx = 0; idx < mbox_nums; idx++) {
mbox_dev = devm_kzalloc(dev, sizeof(*mbox_dev), GFP_KERNEL);
if (IS_ERR(mbox_dev)) {
dev_err(dev, "Failed to alloc mbox_dev\n");
goto out_err;
}
mbox_dev->p_dev = dev;
mbox_dev->dev_t = MKDEV(MAJOR(dev_t), idx);
cdev_init(&mbox_dev->cdev, &mbox_message_ops);
err = cdev_add(&mbox_dev->cdev, mbox_dev->dev_t, 1);
if (err) {
dev_err(dev, "mbox fail to add cdev\n");
goto out_err;
}
if (of_property_read_string_index(dev->of_node,
"mbox-names", idx, &mbox_dev->name)) {
dev_err(dev, "%s get mbox[%d] name fail\n",
__func__, idx);
goto out_err;
}
if (of_property_read_u32_index(dev->of_node, "mbox-dests",
idx, &mbox_dev->dest)) {
dev_err(dev, "%s get mbox[%d] dest fail\n",
__func__, idx);
goto out_err;
}
mbox_dev->dev = device_create(mbox_class, NULL, mbox_dev->dev_t,
mbox_dev, "%s", mbox_dev->name);
if (IS_ERR(mbox_dev->dev)) {
dev_err(dev, "mbox fail to create device\n");
goto out_err;
}
mbox_dev->mbox_chan = aml_mbox_request_channel_byidx(dev, idx);
if (IS_ERR(mbox_dev->mbox_chan)) {
dev_err(dev, "Failed to request mbox chan\n");
goto out_err;
}
}
dev_dbg(dev, "mbox devfs init done\n");
return 0;
out_err:
unregister_chrdev_region(dev_t, mbox_nums);
class_err:
class_destroy(mbox_class);
err:
return err;
}
static int mbox_devfs_probe(struct platform_device *pdev)
{
return mbox_cdev_init(&pdev->dev);
}
static int mbox_devfs_remove(struct platform_device *pdev)
{
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct of_device_id mbox_of_match[] = {
{ .compatible = "amlogic, mbox-devfs" },
{},
};
static struct platform_driver mbox_devfs_drvier = {
.probe = mbox_devfs_probe,
.remove = mbox_devfs_remove,
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
.of_match_table = mbox_of_match,
},
};
int __init mbox_devfs_init(void)
{
int ret = 0;
ret = platform_driver_register(&mbox_devfs_drvier);
return ret;
}
void __exit mbox_devfs_exit(void)
{
platform_driver_unregister(&mbox_devfs_drvier);
}