/* * Copyright (C) 2020 - 2022 MaxLinear, Inc. * Copyright (C) 2016 - 2020 Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, see http://www.gnu.org/licenses/. * * SPDX-License-Identifier: GPL-2.0-only */ /************************************************/ /** Includes */ /************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEV_NAME "docsis_gpio_dev" #define FIRST_MINOR 0 #define MINOR_CNT 1 #define GPIO_CLIENT_MRPC_CALL_TIMEOUT_MS (1000) /* Timeout is needed due to delays in gpio server */ static long gpio_proxy_ioctl(struct file *fd, unsigned int cmd, unsigned long arg); /**************************************************************************/ /* LOCAL VARIABLES: */ /**************************************************************************/ static dev_t dev; static struct cdev *c_dev; static struct class *cl; static struct device *docsis_gpio_dev; /* Structure to map driver functions to kernel */ static struct file_operations gpio_drv_fops = { .owner = THIS_MODULE, .unlocked_ioctl = gpio_proxy_ioctl /*We assume there are no 2 processes in a race condition*/ }; /* driver private database */ struct gpio_private { struct platform_device *pdev; /* platform device */ struct mrpc_client *mrpc; /* mrpc client handle */ }; static struct gpio_private *this; enum { GPIO_SET_DIR_PROC = 0, GPIO_SET_VAL_PROC }; static inline int gpio_mrpc_call(__u8 procid, int gpio, int dir, int value) { struct gpio_private *priv = this; struct gpio_user_info args; int ret, errcode; if (!priv) { pr_err("ERROR: mrpc gpio not initialized"); return GPIO_FAIL; } args.gpio_pin = htonl(gpio); args.pin_direction = htonl(dir); args.value = htonl(value); ret = mrpc_call(priv->mrpc, procid, &args, sizeof(args), NULL, 0, GPIO_CLIENT_MRPC_CALL_TIMEOUT_MS, &errcode); if (ret || errcode) { pr_err("ERROR: ret=%d, errcode=%d", ret, errcode); return GPIO_FAIL; } return GPIO_OK; } int gpio_set_value(int gpio, int value) { return gpio_mrpc_call(GPIO_SET_VAL_PROC, gpio, GPIO_OUTPUT_DIR, value); } EXPORT_SYMBOL(gpio_set_value); int gpio_set_direction(int gpio, int dir) { return gpio_mrpc_call(GPIO_SET_DIR_PROC, gpio, dir, 0); } EXPORT_SYMBOL(gpio_set_direction); /* sysfs for future use */ static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gpio_private *priv = dev_get_drvdata(dev); if (!priv) return -EINVAL; return scnprintf(buf, PAGE_SIZE, "status ok"); } static DEVICE_ATTR(status, S_IRUGO, status_show, NULL); static struct attribute *gpio_attrs[] = { &dev_attr_status.attr, NULL }; static struct attribute_group gpio_attrs_group = { .attrs = gpio_attrs, }; /** * gpio_mrpc_probe * * @param pdev platform device * * @return 0 for success, error code otherwise */ static int gpio_mrpc_probe(struct platform_device *pdev) { struct gpio_private *priv; int ret; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) { pr_err("memory allocation failed"); return -ENOMEM; } dev_set_drvdata(&pdev->dev, priv); priv->pdev = pdev; ret = sysfs_create_group(&priv->pdev->dev.kobj, &gpio_attrs_group); if (ret) { pr_err("sysfs_create_group failed (ret=%d)", ret); return ret; } priv->mrpc = mrpc_client_register(MRPC_RESERVED_ID_GPIO, "gpio"); if (!priv->mrpc) { pr_err("failed to register gpio"); ret = -ENODEV; goto out_remove_group; } this = priv; return 0; out_remove_group: sysfs_remove_group(&priv->pdev->dev.kobj, &gpio_attrs_group); return ret; } /** * gpio_mrpc_remove * * This function is called when the gpio mrpc driver is * removed. * * @param pdev platform device * * @return 0 for success, error code otherwise */ static int gpio_mrpc_remove(struct platform_device *pdev) { struct gpio_private *priv = platform_get_drvdata(pdev); mrpc_client_unregister(priv->mrpc); sysfs_remove_group(&priv->pdev->dev.kobj, &gpio_attrs_group); dev_set_drvdata(&pdev->dev, NULL); this = NULL; return 0; } static struct platform_driver gpio_driver = { .driver = { .name = "gpio", }, .probe = gpio_mrpc_probe, .remove = gpio_mrpc_remove, }; static struct platform_device *gpio_device; static int create_proxy_device(void) { int ret; if ((ret = alloc_chrdev_region(&dev, 0, 1, DEV_NAME)) < 0) { printk(KERN_ERR "gpio: Failed alloc_chrdev_region\n"); return ret; } pr_debug("alloc_chrdev_region - Done"); if (!(c_dev = cdev_alloc())) { printk(KERN_ERR "%s:%d Failed to allocate character device gpio_driver\n", __FUNCTION__, __LINE__); unregister_chrdev_region(dev, MINOR_CNT); return (-1); } pr_debug("cdev_alloc - Done"); /* Init the driver */ cdev_init(c_dev, &gpio_drv_fops); pr_debug("cdev_init - Done"); if ((ret = cdev_add(c_dev, dev, MINOR_CNT)) < 0) { printk(KERN_ERR "%s:%d Failed to add character device gpio_driver\n", __FUNCTION__, __LINE__); cdev_del(c_dev); unregister_chrdev_region(dev, MINOR_CNT); return ret; } pr_debug("cdev_add - Done"); if (IS_ERR(cl = class_create(THIS_MODULE, DEV_NAME))) { printk(KERN_ERR "gpio: class_create failed\n"); cdev_del(c_dev); unregister_chrdev_region(dev, MINOR_CNT); return PTR_ERR(cl); } pr_debug("class_create - Done"); if (IS_ERR(docsis_gpio_dev = device_create(cl, NULL, dev, NULL, DEV_NAME))) { printk(KERN_ERR "device_create failed\n"); class_destroy(cl); cdev_del(c_dev); unregister_chrdev_region(dev, MINOR_CNT); return PTR_ERR(docsis_gpio_dev); } pr_debug("device_create %s - Done", DEV_NAME); return 0; } static int __init gpio_mrpc_init(void) { int ret; ret = platform_driver_register(&gpio_driver); if (ret < 0) { pr_err("Failed to register gpio platform driver: %d\n", ret); return ret; } pr_debug("gpio platform driver is registered"); gpio_device = platform_device_register_simple("gpio", -1, NULL, 0); if (IS_ERR(gpio_device)) { pr_err("Failed to register gpio platform device\n"); platform_driver_unregister(&gpio_driver); return PTR_ERR(gpio_device); } dev_info(&gpio_device->dev, "platform device is registered\n"); return create_proxy_device(); } /**************************************************************************/ /*! \fn static void delete_proxy_device(void) ************************************************************************** * \brief Delete proxy device created in create_proxy_device() * \param[in] none * \return none *************************************************************************/ static void delete_proxy_device(void) { device_del(docsis_gpio_dev); device_destroy(cl, dev); class_destroy(cl); cdev_del(c_dev); unregister_chrdev_region(dev, MINOR_CNT); } static void __exit gpio_mrpc_exit(void) { platform_device_unregister(gpio_device); platform_driver_unregister(&gpio_driver); delete_proxy_device(); } static long gpio_proxy_ioctl(struct file *fd, unsigned int cmd, unsigned long arg) { struct gpio_user_info gpio_info; int ret = 0; /* Check for valid pointer to the parameter list */ if (0 == arg) { printk(KERN_ERR "[%s] Arg == 0\n",__FUNCTION__); return -EINVAL; } memset(&gpio_info, 0, sizeof(gpio_info)); switch (cmd) { case GPIO_SET_DIRECTION_CMD: case GPIO_OUT_BIT_CMD: if (copy_from_user(&gpio_info, (void __user *)arg, sizeof(struct gpio_user_info))) { printk(KERN_ERR "[%s] Copy from user the GPIO data failed\n",__FUNCTION__); return -EFAULT; } break; /*---------------------------------------------------------------------------*/ default: printk(KERN_ERR "[%s] illegal command given 0x%x\n",__FUNCTION__, cmd); return -ENOSYS; break; } switch (cmd) { /*---------------------------------------------------------------------------*/ case GPIO_SET_DIRECTION_CMD: printk(KERN_DEBUG "[%s] Request to set GPIO pin = %d , direction = %d\n",__FUNCTION__,gpio_info.gpio_pin,gpio_info.pin_direction); if (gpio_set_direction(gpio_info.gpio_pin, gpio_info.pin_direction) == GPIO_FAIL) { ret = -ENOSYS; } break; /*---------------------------------------------------------------------------*/ case GPIO_OUT_BIT_CMD: printk(KERN_DEBUG "[%s] Set GPIO pin = %d data out for value = %d\n",__FUNCTION__,gpio_info.gpio_pin,gpio_info.value); if (gpio_set_value(gpio_info.gpio_pin, gpio_info.value) == GPIO_FAIL) { ret = -ENOSYS; } break; /*---------------------------------------------------------------------------*/ default: printk(KERN_ERR "[%s] illegal command given 0x%x\n",__FUNCTION__, cmd); ret = -ENOSYS; break; } return ret; } /******************************************************/ /** Module Declarations **/ /******************************************************/ module_init(gpio_mrpc_init); module_exit(gpio_mrpc_exit); MODULE_AUTHOR("Intel Corporation"); MODULE_DESCRIPTION("gpio mrpc client"); MODULE_LICENSE("GPL"); MODULE_VERSION("1.0");