blob: 5aaa9b7542c9039c66d1f693b09d5bf83acd1733 [file] [log] [blame]
/*
* imx_mcc_tty.c - pty demo driver used to test imx mcc
* posix pty interface.
*
* Copyright (C) 2014 Freescale Semiconductor, Inc.
*/
/*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/mcc_config_linux.h>
#include <linux/mcc_common.h>
#include <linux/mcc_api.h>
/**
* struct mcctty_port - Wrapper struct for imx mcc pty port.
* @port: TTY port data
* @rx_lock: Lock for rx_buf.
* @rx_buf: Read buffer
*/
struct mcctty_port {
struct delayed_work read;
struct tty_port port;
spinlock_t rx_lock;
char *rx_buf;
};
static struct mcctty_port mcc_tty_port;
enum {
MCC_NODE_A9 = 0,
MCC_NODE_M4 = 0,
MCC_A9_PORT = 1,
MCC_M4_PORT = 2,
};
/* mcc tty/pingpong demo */
static MCC_ENDPOINT mcc_endpoint_a9_pingpong = {0, MCC_NODE_A9, MCC_A9_PORT};
static MCC_ENDPOINT mcc_endpoint_m4_pingpong = {1, MCC_NODE_M4, MCC_M4_PORT};
struct mcc_tty_msg {
char data[MCC_ATTR_BUFFER_SIZE_IN_BYTES - 24];
};
static struct tty_port_operations mcctty_port_ops = { };
static int mcctty_install(struct tty_driver *driver, struct tty_struct *tty)
{
return tty_port_install(&mcc_tty_port.port, driver, tty);
}
static int mcctty_open(struct tty_struct *tty, struct file *filp)
{
return tty_port_open(tty->port, tty, filp);
}
static void mcctty_close(struct tty_struct *tty, struct file *filp)
{
return tty_port_close(tty->port, tty, filp);
}
static int mcctty_write(struct tty_struct *tty, const unsigned char *buf,
int total)
{
int i, count, ret = 0, space;
unsigned char *cbuf, *tmp;
MCC_MEM_SIZE num_of_received_bytes;
struct mcc_tty_msg tty_msg;
struct mcctty_port *cport = &mcc_tty_port;
if (NULL == buf) {
pr_err("buf shouldn't be null.\n");
return -ENOMEM;
}
count = total;
tmp = (unsigned char *)buf;
for (i = 0; i <= count / 999; i++) {
strlcpy(tty_msg.data, tmp, count >= 1000 ? 1000 : count + 1);
if (count >= 1000)
count -= 999;
/*
* wait until the remote endpoint is created by
* the other core
*/
ret = mcc_send(&mcc_endpoint_a9_pingpong,
&mcc_endpoint_m4_pingpong, &tty_msg,
sizeof(struct mcc_tty_msg),
0xffffffff);
while (MCC_ERR_ENDPOINT == ret) {
pr_err("\n send err ret %d, re-send\n", ret);
ret = mcc_send(&mcc_endpoint_a9_pingpong,
&mcc_endpoint_m4_pingpong, &tty_msg,
sizeof(struct mcc_tty_msg),
0xffffffff);
msleep(5000);
}
ret = mcc_recv(&mcc_endpoint_m4_pingpong,
&mcc_endpoint_a9_pingpong, &tty_msg,
sizeof(struct mcc_tty_msg),
&num_of_received_bytes, 0xffffffff);
if (MCC_SUCCESS != ret) {
pr_err("A9 Main task receive error: %d\n", ret);
} else {
/* flush the recv-ed data to tty node */
spin_lock_bh(&cport->rx_lock);
space = tty_prepare_flip_string(&cport->port, &cbuf,
strlen(tty_msg.data));
if ((space <= 0) || (cport->rx_buf == NULL))
goto tty_unlock;
memcpy(cport->rx_buf, &tty_msg.data,
strlen(tty_msg.data));
memcpy(cbuf, cport->rx_buf, space);
tty_flip_buffer_push(&cport->port);
tty_unlock:
spin_unlock_bh(&cport->rx_lock);
}
}
return total;
}
static int mcctty_write_room(struct tty_struct *tty)
{
int room;
/* report the space in the mcc buffer */
room = MCC_ATTR_BUFFER_SIZE_IN_BYTES;
return room;
}
static const struct tty_operations imxmcctty_ops = {
.install = mcctty_install,
.open = mcctty_open,
.close = mcctty_close,
.write = mcctty_write,
.write_room = mcctty_write_room,
};
static struct tty_driver *mcctty_driver;
static int imx_mcc_tty_probe(struct platform_device *pdev)
{
int ret;
struct mcctty_port *cport = &mcc_tty_port;
MCC_INFO_STRUCT mcc_info;
mcctty_driver = tty_alloc_driver(1,
TTY_DRIVER_RESET_TERMIOS |
TTY_DRIVER_UNNUMBERED_NODE);
if (IS_ERR(mcctty_driver))
return PTR_ERR(mcctty_driver);
mcctty_driver->driver_name = "mcc_tty";
mcctty_driver->name = "ttyMCC";
mcctty_driver->major = TTYAUX_MAJOR;
mcctty_driver->minor_start = 3;
mcctty_driver->type = TTY_DRIVER_TYPE_PTY;
mcctty_driver->init_termios = tty_std_termios;
mcctty_driver->init_termios.c_cflag |= CLOCAL;
tty_set_operations(mcctty_driver, &imxmcctty_ops);
tty_port_init(&cport->port);
cport->port.ops = &mcctty_port_ops;
spin_lock_init(&cport->rx_lock);
ret = tty_register_driver(mcctty_driver);
if (ret < 0) {
pr_err("Couldn't install mcc tty driver: err %d\n", ret);
goto error;
} else
pr_info("Install mcc tty driver!\n");
/* Allocate the buffer we use for reading data */
cport->rx_buf = kzalloc(MCC_ATTR_BUFFER_SIZE_IN_BYTES,
GFP_KERNEL);
if (!cport->rx_buf) {
ret = -ENOMEM;
goto error;
}
ret = mcc_initialize(MCC_NODE_A9);
if (ret) {
pr_err("failed to initialize mcc.\n");
ret = -ENODEV;
goto error;
}
ret = mcc_get_info(MCC_NODE_A9, &mcc_info);
if (ret) {
pr_err("failed to get mcc info.\n");
ret = -ENODEV;
goto error;
} else {
pr_info("\nA9 mcc prepares run, MCC version is %s\n",
mcc_info.version_string);
pr_info("imx mcc tty/pingpong test begin.\n");
}
ret = mcc_create_endpoint(&mcc_endpoint_a9_pingpong,
MCC_A9_PORT);
if (ret) {
pr_err("failed to create a9 mcc ep.\n");
ret = -ENODEV;
goto error;
}
return 0;
error:
tty_unregister_driver(mcctty_driver);
put_tty_driver(mcctty_driver);
tty_port_destroy(&cport->port);
mcctty_driver = NULL;
return ret;
}
static int imx_mcc_tty_remove(struct platform_device *pdev)
{
int ret = 0;
struct mcctty_port *cport = &mcc_tty_port;
/* stop reading, null the read buffer. */
kfree(cport->rx_buf);
cport->rx_buf = NULL;
/* destory the mcc tty endpoint here */
ret = mcc_destroy_endpoint(&mcc_endpoint_a9_pingpong);
if (ret)
pr_err("failed to destory a9 mcc ep.\n");
else
pr_info("destory a9 mcc ep.\n");
tty_unregister_driver(mcctty_driver);
tty_port_destroy(&cport->port);
put_tty_driver(mcctty_driver);
return ret;
}
static const struct of_device_id imx6sx_mcc_tty_ids[] = {
{ .compatible = "fsl,imx6sx-mcc-tty", },
{ /* sentinel */ }
};
static struct platform_driver imxmcctty_driver = {
.driver = {
.name = "imx6sx-mcc-tty",
.owner = THIS_MODULE,
.of_match_table = imx6sx_mcc_tty_ids,
},
.probe = imx_mcc_tty_probe,
.remove = imx_mcc_tty_remove,
};
/*!
* Initialise the imxmcctty_driver.
*
* @return The function always returns 0.
*/
static int __init imxmcctty_init(void)
{
if (platform_driver_register(&imxmcctty_driver) != 0)
return -ENODEV;
printk(KERN_INFO "IMX MCC TTY driver module loaded\n");
return 0;
}
static void __exit imxmcctty_exit(void)
{
/* Unregister the device structure */
platform_driver_unregister(&imxmcctty_driver);
}
module_init(imxmcctty_init);
module_exit(imxmcctty_exit);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MCC TTY driver");
MODULE_LICENSE("GPL");