425 lines
10 KiB
C
425 lines
10 KiB
C
/*
|
|
* Copyright 2017 NXP
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/imx_rpmsg.h>
|
|
#include <linux/init.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_qos.h>
|
|
#include <linux/rpmsg.h>
|
|
#include <linux/virtio.h>
|
|
|
|
#define IMX_RPMSG_GPIO_PER_PORT 32
|
|
#define RPMSG_TIMEOUT 1000
|
|
|
|
enum gpio_input_trigger_type {
|
|
GPIO_RPMSG_TRI_IGNORE,
|
|
GPIO_RPMSG_TRI_RISING,
|
|
GPIO_RPMSG_TRI_FALLING,
|
|
GPIO_RPMSG_TRI_BOTH_EDGE,
|
|
GPIO_RPMSG_TRI_LOW_LEVEL,
|
|
GPIO_RPMSG_TRI_HIGH_LEVEL,
|
|
};
|
|
|
|
enum gpio_rpmsg_header_type {
|
|
GPIO_RPMSG_SETUP,
|
|
GPIO_RPMSG_REPLY,
|
|
GPIO_RPMSG_NOTIFY,
|
|
};
|
|
|
|
enum gpio_rpmsg_header_cmd {
|
|
GPIO_RPMSG_INPUT_INIT,
|
|
GPIO_RPMSG_OUTPUT_INIT,
|
|
GPIO_RPMSG_INPUT_GET,
|
|
};
|
|
|
|
struct gpio_rpmsg_data {
|
|
struct imx_rpmsg_head header;
|
|
u8 pin_idx;
|
|
u8 port_idx;
|
|
union {
|
|
u8 event;
|
|
u8 retcode;
|
|
u8 value;
|
|
} out;
|
|
union {
|
|
u8 wakeup;
|
|
u8 value;
|
|
} in;
|
|
} __packed __aligned(8);
|
|
|
|
struct imx_rpmsg_gpio_port {
|
|
struct gpio_chip gc;
|
|
struct irq_chip chip;
|
|
struct irq_domain *domain;
|
|
struct gpio_rpmsg_data msg;
|
|
u32 irq_type[IMX_RPMSG_GPIO_PER_PORT];
|
|
int idx;
|
|
};
|
|
|
|
struct imx_gpio_rpmsg_info {
|
|
struct rpmsg_device *rpdev;
|
|
struct gpio_rpmsg_data *notify_msg;
|
|
struct gpio_rpmsg_data *reply_msg;
|
|
struct pm_qos_request pm_qos_req;
|
|
struct completion cmd_complete;
|
|
struct mutex lock;
|
|
};
|
|
|
|
static struct imx_gpio_rpmsg_info gpio_rpmsg;
|
|
|
|
static int gpio_send_message(struct imx_rpmsg_gpio_port *port,
|
|
struct gpio_rpmsg_data *msg,
|
|
struct imx_gpio_rpmsg_info *info,
|
|
bool sync)
|
|
{
|
|
int err;
|
|
|
|
if (!info->rpdev) {
|
|
dev_dbg(&info->rpdev->dev,
|
|
"rpmsg channel not ready, m4 image ready?\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&info->lock);
|
|
pm_qos_add_request(&info->pm_qos_req,
|
|
PM_QOS_CPU_DMA_LATENCY, 0);
|
|
|
|
reinit_completion(&info->cmd_complete);
|
|
|
|
err = rpmsg_send(info->rpdev->ept, (void *)msg,
|
|
sizeof(struct gpio_rpmsg_data));
|
|
|
|
if (err) {
|
|
dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err);
|
|
goto err_out;
|
|
}
|
|
|
|
if (sync) {
|
|
err = wait_for_completion_timeout(&info->cmd_complete,
|
|
msecs_to_jiffies(RPMSG_TIMEOUT));
|
|
if (!err) {
|
|
dev_err(&info->rpdev->dev, "rpmsg_send timeout!\n");
|
|
err = -ETIMEDOUT;
|
|
goto err_out;
|
|
}
|
|
|
|
if (info->reply_msg->out.retcode != 0) {
|
|
dev_err(&info->rpdev->dev, "rpmsg not ack %d!\n",
|
|
info->reply_msg->out.retcode);
|
|
err = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
/* copy the reply message */
|
|
memcpy(&port->msg, info->reply_msg, sizeof(*info->reply_msg));
|
|
|
|
err = 0;
|
|
}
|
|
|
|
err_out:
|
|
pm_qos_remove_request(&info->pm_qos_req);
|
|
mutex_unlock(&info->lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int gpio_rpmsg_cb(struct rpmsg_device *rpdev,
|
|
void *data, int len, void *priv, u32 src)
|
|
{
|
|
struct gpio_rpmsg_data *msg = (struct gpio_rpmsg_data *)data;
|
|
|
|
if (msg->header.type == GPIO_RPMSG_REPLY) {
|
|
gpio_rpmsg.reply_msg = msg;
|
|
complete(&gpio_rpmsg.cmd_complete);
|
|
} else if (msg->header.type == GPIO_RPMSG_NOTIFY) {
|
|
gpio_rpmsg.notify_msg = msg;
|
|
/* TBD for interrupt handler */
|
|
} else
|
|
dev_err(&gpio_rpmsg.rpdev->dev, "wrong command type!\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int imx_rpmsg_gpio_get(struct gpio_chip *gc, unsigned int gpio)
|
|
{
|
|
struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc);
|
|
struct gpio_rpmsg_data msg;
|
|
int ret;
|
|
|
|
memset(&msg, 0, sizeof(struct gpio_rpmsg_data));
|
|
msg.header.cate = IMX_RPMSG_GPIO;
|
|
msg.header.major = IMX_RMPSG_MAJOR;
|
|
msg.header.minor = IMX_RMPSG_MINOR;
|
|
msg.header.type = GPIO_RPMSG_SETUP;
|
|
msg.header.cmd = GPIO_RPMSG_INPUT_GET;
|
|
msg.pin_idx = gpio;
|
|
msg.port_idx = port->idx;
|
|
|
|
ret = gpio_send_message(port, &msg, &gpio_rpmsg, true);
|
|
if (!ret)
|
|
return !!port->msg.in.value;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int imx_rpmsg_gpio_direction_input(struct gpio_chip *gc,
|
|
unsigned int gpio)
|
|
{
|
|
struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc);
|
|
struct gpio_rpmsg_data msg;
|
|
|
|
memset(&msg, 0, sizeof(struct gpio_rpmsg_data));
|
|
msg.header.cate = IMX_RPMSG_GPIO;
|
|
msg.header.major = IMX_RMPSG_MAJOR;
|
|
msg.header.minor = IMX_RMPSG_MINOR;
|
|
msg.header.type = GPIO_RPMSG_SETUP;
|
|
msg.header.cmd = GPIO_RPMSG_INPUT_INIT;
|
|
msg.pin_idx = gpio;
|
|
msg.port_idx = port->idx;
|
|
|
|
/* TBD: get event trigger and wakeup from GPIO descriptor */
|
|
msg.out.event = GPIO_RPMSG_TRI_IGNORE;
|
|
msg.in.wakeup = 0;
|
|
|
|
return gpio_send_message(port, &msg, &gpio_rpmsg, true);
|
|
}
|
|
|
|
static inline void imx_rpmsg_gpio_direction_output_init(struct gpio_chip *gc,
|
|
unsigned int gpio, int val, struct gpio_rpmsg_data *msg)
|
|
{
|
|
struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc);
|
|
|
|
msg->header.cate = IMX_RPMSG_GPIO;
|
|
msg->header.major = IMX_RMPSG_MAJOR;
|
|
msg->header.minor = IMX_RMPSG_MINOR;
|
|
msg->header.type = GPIO_RPMSG_SETUP;
|
|
msg->header.cmd = GPIO_RPMSG_OUTPUT_INIT;
|
|
msg->pin_idx = gpio;
|
|
msg->port_idx = port->idx;
|
|
msg->out.value = val;
|
|
}
|
|
|
|
static void imx_rpmsg_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
|
|
{
|
|
struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc);
|
|
struct gpio_rpmsg_data msg;
|
|
|
|
memset(&msg, 0, sizeof(struct gpio_rpmsg_data));
|
|
imx_rpmsg_gpio_direction_output_init(gc, gpio, val, &msg);
|
|
gpio_send_message(port, &msg, &gpio_rpmsg, true);
|
|
}
|
|
|
|
static int imx_rpmsg_gpio_direction_output(struct gpio_chip *gc,
|
|
unsigned int gpio, int val)
|
|
{
|
|
struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc);
|
|
struct gpio_rpmsg_data msg;
|
|
|
|
memset(&msg, 0, sizeof(struct gpio_rpmsg_data));
|
|
imx_rpmsg_gpio_direction_output_init(gc, gpio, val, &msg);
|
|
return gpio_send_message(port, &msg, &gpio_rpmsg, true);
|
|
}
|
|
|
|
static int imx_rpmsg_irq_set_type(struct irq_data *d, u32 type)
|
|
{
|
|
struct imx_rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d);
|
|
u32 gpio_idx = d->hwirq;
|
|
int edge = 0;
|
|
int ret = 0;
|
|
|
|
switch (type) {
|
|
case IRQ_TYPE_EDGE_RISING:
|
|
edge = GPIO_RPMSG_TRI_RISING;
|
|
break;
|
|
case IRQ_TYPE_EDGE_FALLING:
|
|
edge = GPIO_RPMSG_TRI_FALLING;
|
|
break;
|
|
case IRQ_TYPE_EDGE_BOTH:
|
|
edge = GPIO_RPMSG_TRI_BOTH_EDGE;
|
|
break;
|
|
case IRQ_TYPE_LEVEL_LOW:
|
|
edge = GPIO_RPMSG_TRI_LOW_LEVEL;
|
|
break;
|
|
case IRQ_TYPE_LEVEL_HIGH:
|
|
edge = GPIO_RPMSG_TRI_HIGH_LEVEL;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
port->irq_type[gpio_idx] = edge;
|
|
return ret;
|
|
}
|
|
|
|
static int imx_rpmsg_irq_set_wake(struct irq_data *d, u32 enable)
|
|
{
|
|
struct imx_rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d);
|
|
struct gpio_rpmsg_data msg;
|
|
u32 gpio_idx = d->hwirq;
|
|
|
|
memset(&msg, 0, sizeof(struct gpio_rpmsg_data));
|
|
msg.header.cate = IMX_RPMSG_GPIO;
|
|
msg.header.major = IMX_RMPSG_MAJOR;
|
|
msg.header.minor = IMX_RMPSG_MINOR;
|
|
msg.header.type = GPIO_RPMSG_SETUP;
|
|
msg.header.cmd = GPIO_RPMSG_INPUT_INIT;
|
|
msg.pin_idx = gpio_idx;
|
|
msg.port_idx = port->idx;
|
|
|
|
/* set wakeup trigger source,
|
|
* if not set irq type, then use high level as trigger type
|
|
*/
|
|
msg.out.event = port->irq_type[gpio_idx];
|
|
if (!msg.out.event)
|
|
msg.out.event = GPIO_RPMSG_TRI_HIGH_LEVEL;
|
|
|
|
msg.in.wakeup = enable;
|
|
|
|
/* here should be atomic context */
|
|
gpio_send_message(port, &msg, &gpio_rpmsg, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void imx_rpmsg_unmask_irq(struct irq_data *d)
|
|
{
|
|
/* No need to implement the callback */
|
|
}
|
|
|
|
static void imx_rpmsg_mask_irq(struct irq_data *d)
|
|
{
|
|
/* No need to implement the callback */
|
|
}
|
|
|
|
static struct irq_chip imx_rpmsg_irq_chip = {
|
|
.irq_mask = imx_rpmsg_mask_irq,
|
|
.irq_unmask = imx_rpmsg_unmask_irq,
|
|
.irq_set_wake = imx_rpmsg_irq_set_wake,
|
|
.irq_set_type = imx_rpmsg_irq_set_type,
|
|
};
|
|
|
|
static int imx_rpmsg_gpio_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct imx_rpmsg_gpio_port *port;
|
|
struct gpio_chip *gc;
|
|
int i, irq_base;
|
|
int ret;
|
|
|
|
port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
|
|
if (!port)
|
|
return -ENOMEM;
|
|
|
|
ret = of_property_read_u32(np, "port_idx", &port->idx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
gc = &port->gc;
|
|
gc->of_node = np;
|
|
gc->parent = dev;
|
|
gc->label = "imx-rpmsg-gpio";
|
|
gc->ngpio = IMX_RPMSG_GPIO_PER_PORT;
|
|
gc->base = of_alias_get_id(np, "gpio") * IMX_RPMSG_GPIO_PER_PORT;
|
|
|
|
gc->direction_input = imx_rpmsg_gpio_direction_input;
|
|
gc->direction_output = imx_rpmsg_gpio_direction_output;
|
|
gc->get = imx_rpmsg_gpio_get;
|
|
gc->set = imx_rpmsg_gpio_set;
|
|
|
|
platform_set_drvdata(pdev, port);
|
|
|
|
ret = devm_gpiochip_add_data(dev, gc, port);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* generate one new irq domain */
|
|
port->chip = imx_rpmsg_irq_chip;
|
|
port->chip.name = kasprintf(GFP_KERNEL, "rpmsg-irq-port-%d", port->idx);
|
|
port->chip.parent_device = NULL;
|
|
|
|
irq_base = irq_alloc_descs(-1, 0, IMX_RPMSG_GPIO_PER_PORT,
|
|
numa_node_id());
|
|
WARN_ON(irq_base < 0);
|
|
|
|
port->domain = irq_domain_add_legacy(np, IMX_RPMSG_GPIO_PER_PORT,
|
|
irq_base, 0,
|
|
&irq_domain_simple_ops, port);
|
|
WARN_ON(!port->domain);
|
|
for (i = irq_base; i < irq_base + IMX_RPMSG_GPIO_PER_PORT; i++) {
|
|
irq_set_chip_and_handler(i, &port->chip, handle_level_irq);
|
|
irq_set_chip_data(i, port);
|
|
irq_clear_status_flags(i, IRQ_NOREQUEST);
|
|
irq_set_probe(i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
static const struct of_device_id imx_rpmsg_gpio_dt_ids[] = {
|
|
{ .compatible = "fsl,imx-rpmsg-gpio" },
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
static struct platform_driver imx_rpmsg_gpio_driver = {
|
|
.driver = {
|
|
.name = "gpio-imx-rpmsg",
|
|
.of_match_table = imx_rpmsg_gpio_dt_ids,
|
|
},
|
|
.probe = imx_rpmsg_gpio_probe,
|
|
};
|
|
|
|
static int gpio_rpmsg_probe(struct rpmsg_device *rpdev)
|
|
{
|
|
gpio_rpmsg.rpdev = rpdev;
|
|
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
|
|
rpdev->src, rpdev->dst);
|
|
|
|
init_completion(&gpio_rpmsg.cmd_complete);
|
|
mutex_init(&gpio_rpmsg.lock);
|
|
|
|
return platform_driver_register(&imx_rpmsg_gpio_driver);
|
|
}
|
|
|
|
static struct rpmsg_device_id gpio_rpmsg_id_table[] = {
|
|
{ .name = "rpmsg-io-channel" },
|
|
{},
|
|
};
|
|
|
|
static struct rpmsg_driver gpio_rpmsg_driver = {
|
|
.drv.name = "gpio_rpmsg",
|
|
.drv.owner = THIS_MODULE,
|
|
.id_table = gpio_rpmsg_id_table,
|
|
.probe = gpio_rpmsg_probe,
|
|
.callback = gpio_rpmsg_cb,
|
|
};
|
|
|
|
|
|
static int __init gpio_imx_rpmsg_init(void)
|
|
{
|
|
return register_rpmsg_driver(&gpio_rpmsg_driver);
|
|
}
|
|
device_initcall(gpio_imx_rpmsg_init);
|
|
|
|
MODULE_AUTHOR("NXP Semiconductor");
|
|
MODULE_DESCRIPTION("NXP i.MX7ULP rpmsg gpio driver");
|
|
MODULE_LICENSE("GPL v2");
|