1
0
Fork 0
alistair23-linux/drivers/staging/fsl_ppfe/pfe_cdev.c

259 lines
6.1 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 NXP
*/
/* @pfe_cdev.c.
* Dummy device representing the PFE US in userspace.
* - used for interacting with the kernel layer for link status
*/
#include <linux/eventfd.h>
#include <linux/irqreturn.h>
#include <linux/io.h>
#include <asm/irq.h>
#include "pfe_cdev.h"
#include "pfe_mod.h"
static int pfe_majno;
static struct class *pfe_char_class;
static struct device *pfe_char_dev;
struct eventfd_ctx *g_trigger;
struct pfe_shared_info link_states[PFE_CDEV_ETH_COUNT];
static int pfe_cdev_open(struct inode *inp, struct file *fp)
{
pr_debug("PFE CDEV device opened.\n");
return 0;
}
static ssize_t pfe_cdev_read(struct file *fp, char *buf,
size_t len, loff_t *off)
{
int ret = 0;
pr_info("PFE CDEV attempt copying (%lu) size of user.\n",
sizeof(link_states));
pr_debug("Dump link_state on screen before copy_to_user\n");
for (; ret < PFE_CDEV_ETH_COUNT; ret++) {
pr_debug("%u %u", link_states[ret].phy_id,
link_states[ret].state);
pr_debug("\n");
}
/* Copy to user the value in buffer sized len */
ret = copy_to_user(buf, &link_states, sizeof(link_states));
if (ret != 0) {
pr_err("Failed to send (%d)bytes of (%lu) requested.\n",
ret, len);
return -EFAULT;
}
/* offset set back to 0 as there is contextual reading offset */
*off = 0;
pr_debug("Read of (%lu) bytes performed.\n", sizeof(link_states));
return sizeof(link_states);
}
/**
* This function is for getting some commands from user through non-IOCTL
* channel. It can used to configure the device.
* TODO: To be filled in future, if require duplex communication with user
* space.
*/
static ssize_t pfe_cdev_write(struct file *fp, const char *buf,
size_t len, loff_t *off)
{
pr_info("PFE CDEV Write operation not supported!\n");
return -EFAULT;
}
static int pfe_cdev_release(struct inode *inp, struct file *fp)
{
if (g_trigger) {
free_irq(pfe->hif_irq, g_trigger);
eventfd_ctx_put(g_trigger);
g_trigger = NULL;
}
pr_info("PFE_CDEV: Device successfully closed\n");
return 0;
}
/*
* hif_us_isr-
* This ISR routine processes Rx/Tx done interrupts from the HIF hardware block
*/
static irqreturn_t hif_us_isr(int irq, void *arg)
{
struct eventfd_ctx *trigger = (struct eventfd_ctx *)arg;
int int_status;
int int_enable_mask;
/*Read hif interrupt source register */
int_status = readl_relaxed(HIF_INT_SRC);
int_enable_mask = readl_relaxed(HIF_INT_ENABLE);
if ((int_status & HIF_INT) == 0)
return IRQ_NONE;
if (int_status & HIF_RXPKT_INT) {
int_enable_mask &= ~(HIF_RXPKT_INT);
/* Disable interrupts, they will be enabled after
* they are serviced
*/
writel_relaxed(int_enable_mask, HIF_INT_ENABLE);
eventfd_signal(trigger, 1);
}
return IRQ_HANDLED;
}
#define PFE_INTR_COAL_USECS 100
static long pfe_cdev_ioctl(struct file *fp, unsigned int cmd,
unsigned long arg)
{
int ret = -EFAULT;
int __user *argp = (int __user *)arg;
pr_debug("PFE CDEV IOCTL Called with cmd=(%u)\n", cmd);
switch (cmd) {
case PFE_CDEV_ETH0_STATE_GET:
/* Return an unsigned int (link state) for ETH0 */
*argp = link_states[0].state;
pr_debug("Returning state=%d for ETH0\n", *argp);
ret = 0;
break;
case PFE_CDEV_ETH1_STATE_GET:
/* Return an unsigned int (link state) for ETH0 */
*argp = link_states[1].state;
pr_debug("Returning state=%d for ETH1\n", *argp);
ret = 0;
break;
case PFE_CDEV_HIF_INTR_EN:
/* Return success/failure */
g_trigger = eventfd_ctx_fdget(*argp);
if (IS_ERR(g_trigger))
return PTR_ERR(g_trigger);
ret = request_irq(pfe->hif_irq, hif_us_isr, 0, "pfe_hif",
g_trigger);
if (ret) {
pr_err("%s: failed to get the hif IRQ = %d\n",
__func__, pfe->hif_irq);
eventfd_ctx_put(g_trigger);
g_trigger = NULL;
}
writel((PFE_INTR_COAL_USECS * (pfe->ctrl.sys_clk / 1000)) |
HIF_INT_COAL_ENABLE, HIF_INT_COAL);
pr_debug("request_irq for hif interrupt: %d\n", pfe->hif_irq);
ret = 0;
break;
default:
pr_info("Unsupport cmd (%d) for PFE CDEV.\n", cmd);
break;
};
return ret;
}
static unsigned int pfe_cdev_poll(struct file *fp,
struct poll_table_struct *wait)
{
pr_info("PFE CDEV poll method not supported\n");
return 0;
}
static const struct file_operations pfe_cdev_fops = {
.open = pfe_cdev_open,
.read = pfe_cdev_read,
.write = pfe_cdev_write,
.release = pfe_cdev_release,
.unlocked_ioctl = pfe_cdev_ioctl,
.poll = pfe_cdev_poll,
};
int pfe_cdev_init(void)
{
int ret;
pr_debug("PFE CDEV initialization begin\n");
/* Register the major number for the device */
pfe_majno = register_chrdev(0, PFE_CDEV_NAME, &pfe_cdev_fops);
if (pfe_majno < 0) {
pr_err("Unable to register PFE CDEV. PFE CDEV not available\n");
ret = pfe_majno;
goto cleanup;
}
pr_debug("PFE CDEV assigned major number: %d\n", pfe_majno);
/* Register the class for the device */
pfe_char_class = class_create(THIS_MODULE, PFE_CLASS_NAME);
if (IS_ERR(pfe_char_class)) {
pr_err(
"Failed to init class for PFE CDEV. PFE CDEV not available.\n");
goto cleanup;
}
pr_debug("PFE CDEV Class created successfully.\n");
/* Create the device without any parent and without any callback data */
pfe_char_dev = device_create(pfe_char_class, NULL,
MKDEV(pfe_majno, 0), NULL,
PFE_CDEV_NAME);
if (IS_ERR(pfe_char_dev)) {
pr_err("Unable to PFE CDEV device. PFE CDEV not available.\n");
ret = PTR_ERR(pfe_char_dev);
goto cleanup;
}
/* Information structure being shared with the userspace */
memset(link_states, 0, sizeof(struct pfe_shared_info) *
PFE_CDEV_ETH_COUNT);
pr_info("PFE CDEV created: %s\n", PFE_CDEV_NAME);
ret = 0;
return ret;
cleanup:
if (!IS_ERR(pfe_char_class))
class_destroy(pfe_char_class);
if (pfe_majno > 0)
unregister_chrdev(pfe_majno, PFE_CDEV_NAME);
ret = -EFAULT;
return ret;
}
void pfe_cdev_exit(void)
{
if (!IS_ERR(pfe_char_dev))
device_destroy(pfe_char_class, MKDEV(pfe_majno, 0));
if (!IS_ERR(pfe_char_class)) {
class_unregister(pfe_char_class);
class_destroy(pfe_char_class);
}
if (pfe_majno > 0)
unregister_chrdev(pfe_majno, PFE_CDEV_NAME);
/* reset the variables */
pfe_majno = 0;
pfe_char_class = NULL;
pfe_char_dev = NULL;
pr_info("PFE CDEV Removed.\n");
}