| /*Copyright (c) 2019, The Linux Foundation. All rights reserved. |
| |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| |
| * 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. |
| */ |
| |
| /*!@file: DWC_ETH_QOS_poll_support.c |
| */ |
| |
| #include "DWC_ETH_QOS_yheader.h" |
| |
| #define AVB_CLASS_A_CHANNEL_NUM 2 |
| #define AVB_CLASS_B_CHANNEL_NUM 3 |
| |
| extern struct DWC_ETH_QOS_prv_data *gDWC_ETH_QOS_prv_data; |
| |
| struct pps_info { |
| int channel_no; |
| }; |
| |
| bool avb_class_a_msg_wq_flag; |
| bool avb_class_b_msg_wq_flag; |
| |
| DECLARE_WAIT_QUEUE_HEAD(avb_class_a_msg_wq); |
| DECLARE_WAIT_QUEUE_HEAD(avb_class_b_msg_wq); |
| |
| static ssize_t pps_fops_read(struct file *filp, char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| |
| unsigned int len = 0, buf_len = 5000; |
| char* temp_buf; |
| ssize_t ret_cnt; |
| struct pps_info *info; |
| |
| info = filp->private_data; |
| |
| if (info->channel_no == AVB_CLASS_A_CHANNEL_NUM ) { |
| temp_buf = kzalloc(buf_len, GFP_KERNEL); |
| if (!temp_buf) |
| return -ENOMEM; |
| |
| if (gDWC_ETH_QOS_prv_data) |
| len = scnprintf(temp_buf, buf_len , |
| "%ld\n", gDWC_ETH_QOS_prv_data->avb_class_a_intr_cnt); |
| else |
| len = scnprintf(temp_buf, buf_len , "0\n"); |
| |
| ret_cnt = simple_read_from_buffer(buf, count, f_pos, temp_buf, len); |
| kfree(temp_buf); |
| EMACERR("poll pps2intr info=%d sent by kernel\n", gDWC_ETH_QOS_prv_data->avb_class_a_intr_cnt); |
| } else if (info->channel_no == AVB_CLASS_B_CHANNEL_NUM ) { |
| temp_buf = kzalloc(buf_len, GFP_KERNEL); |
| if (!temp_buf) |
| return -ENOMEM; |
| |
| if (gDWC_ETH_QOS_prv_data) |
| len = scnprintf(temp_buf, buf_len , |
| "%ld\n", gDWC_ETH_QOS_prv_data->avb_class_b_intr_cnt); |
| else |
| len = scnprintf(temp_buf, buf_len , "0\n"); |
| |
| ret_cnt = simple_read_from_buffer(buf, count, f_pos, temp_buf, len); |
| kfree(temp_buf); |
| |
| } else { |
| EMACERR("invalid channel %d\n",info->channel_no); |
| } |
| return ret_cnt; |
| |
| } |
| |
| static unsigned int pps_fops_poll(struct file *file, poll_table *wait) |
| { |
| unsigned int mask = 0; |
| struct pps_info *info; |
| |
| info = file->private_data; |
| if (info->channel_no == AVB_CLASS_A_CHANNEL_NUM ){ |
| EMACDBG("avb_class_a_fops_poll wait\n"); |
| |
| poll_wait(file, &avb_class_a_msg_wq, wait); |
| |
| EMACDBG("avb_class_a_fops_poll exit\n"); |
| |
| if (avb_class_a_msg_wq_flag == 1) { |
| //Sending read mask |
| mask |= POLLIN | POLLRDNORM; |
| avb_class_a_msg_wq_flag = 0; |
| } |
| } else if (info->channel_no == AVB_CLASS_B_CHANNEL_NUM) { |
| EMACDBG("avb_class_b_fops_poll wait\n"); |
| |
| poll_wait(file, &avb_class_b_msg_wq, wait); |
| |
| EMACDBG("avb_class_b_fops_poll exit\n"); |
| |
| if (avb_class_b_msg_wq_flag == 1) { |
| //Sending read mask |
| mask |= POLLIN | POLLRDNORM; |
| avb_class_b_msg_wq_flag = 0; |
| } |
| } else { |
| EMACERR("invalid channel %d\n",info->channel_no); |
| } |
| return mask; |
| } |
| |
| int pps_open(struct inode *inode, struct file *file) |
| { |
| struct pps_info *info; |
| |
| EMACDBG("pps_open enter\n"); |
| |
| info = kmalloc(sizeof(*info), GFP_KERNEL); |
| if (!info) |
| return -ENOMEM; |
| |
| if (!strncmp(file->f_path.dentry->d_iname, AVB_CLASS_A_POLL_DEV_NODE_NAME , strlen (AVB_CLASS_A_POLL_DEV_NODE_NAME))) { |
| EMACDBG("pps_open file name =%s \n",file->f_path.dentry->d_iname); |
| info->channel_no = AVB_CLASS_A_CHANNEL_NUM; |
| } else if (!strncmp(file->f_path.dentry->d_iname, AVB_CLASS_B_POLL_DEV_NODE_NAME , strlen (AVB_CLASS_B_POLL_DEV_NODE_NAME))) { |
| EMACDBG("pps_open file name =%s \n",file->f_path.dentry->d_iname); |
| info->channel_no = AVB_CLASS_B_CHANNEL_NUM; |
| } else { |
| |
| EMACDBG("stsrncmp failed for %s\n",file->f_path.dentry->d_iname); |
| } |
| file->private_data = info; |
| return 0; |
| } |
| |
| int pps_release(struct inode *inode, struct file *file) |
| { |
| kfree(file->private_data); |
| return 0; |
| } |
| |
| |
| static const struct file_operations pps_fops = { |
| .owner = THIS_MODULE, |
| .open = pps_open, |
| .release = pps_release, |
| .read = pps_fops_read, |
| .poll = pps_fops_poll, |
| }; |
| |
| int create_pps_interrupt_info_device_node(dev_t *pps_dev_t, struct cdev* pps_cdev, |
| struct class* pps_class, char *pps_dev_node_name) |
| { |
| int ret; |
| EMACDBG("create_pps_interrupt_info_device_node enter \n"); |
| |
| ret = alloc_chrdev_region(pps_dev_t, 0, 1, |
| pps_dev_node_name); |
| if (ret) { |
| EMACERR("alloc_chrdev_region error for node %s \n", pps_dev_node_name); |
| goto alloc_chrdev1_region_fail; |
| } |
| |
| pps_cdev = cdev_alloc(); |
| if(!pps_cdev) { |
| ret = -ENOMEM; |
| EMACERR("failed to alloc cdev\n"); |
| goto fail_alloc_cdev; |
| } |
| cdev_init(pps_cdev, &pps_fops); |
| |
| ret = cdev_add(pps_cdev, *pps_dev_t, 1); |
| if (ret < 0) { |
| EMACERR(":cdev_add err=%d\n", -ret); |
| goto cdev1_add_fail; |
| } |
| |
| pps_class = class_create(THIS_MODULE, pps_dev_node_name); |
| if(!pps_class) { |
| ret = -ENODEV; |
| EMACERR("failed to create class\n"); |
| goto fail_create_class; |
| } |
| |
| if (!device_create(pps_class, NULL, |
| *pps_dev_t, NULL, pps_dev_node_name)) { |
| ret = -EINVAL; |
| EMACERR("failed to create device_create\n"); |
| goto fail_create_device; |
| } |
| |
| EMACDBG("create_pps_interrupt_info_device_node exit successfuly \n"); |
| |
| return 0; |
| |
| fail_create_device: |
| class_destroy(pps_class); |
| fail_create_class: |
| cdev_del(pps_cdev); |
| cdev1_add_fail: |
| fail_alloc_cdev: |
| unregister_chrdev_region(*pps_dev_t, 1); |
| alloc_chrdev1_region_fail: |
| return ret; |
| |
| } |
| |
| int remove_pps_interrupt_info_device_node(struct DWC_ETH_QOS_prv_data *pdata) |
| { |
| device_destroy(pdata->avb_class_a_class, pdata->avb_class_a_dev_t); |
| class_destroy(pdata->avb_class_a_class); |
| cdev_del(pdata->avb_class_a_cdev); |
| unregister_chrdev_region(pdata->avb_class_a_dev_t, 1); |
| |
| device_destroy(pdata->avb_class_b_class, pdata->avb_class_b_dev_t); |
| class_destroy(pdata->avb_class_b_class); |
| cdev_del(pdata->avb_class_b_cdev); |
| unregister_chrdev_region(pdata->avb_class_b_dev_t, 1); |
| return 0; |
| } |
| |
| |