/* * * modphy_client.c * Description: * modphy mrpc client driver * * * GPL LICENSE SUMMARY * * Copyright(c) 2016-2018 Intel Corporation. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License 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, write to the Free Software * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * The full GNU General Public License is included in this distribution * in the file called LICENSE.GPL. * * Contact Information: * Intel Corporation * 2200 Mission College Blvd. * Santa Clara, CA 97052 */ #define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d " fmt "\n", __func__, __LINE__ /************************************************/ /** Includes */ /************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FIRST_MINOR 0 #define MINOR_CNT 1 #ifdef DEBUG #define DBG(fmt, ...) pr_err(fmt, ##__VA_ARGS__) #else #define DBG(fmt, ...) pr_debug(fmt, ##__VA_ARGS__) #endif #define MODPHY_CLIENT_MRPC_CALL_TIMEOUT_MS (1000) /* Timeout is needed due to delays in modphy server */ static long modphy_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; /* Structure to map driver functions to kernel */ static struct file_operations modphy_drv_fops = { .owner = THIS_MODULE, .unlocked_ioctl = modphy_proxy_ioctl /*We assume there are no 2 processes in a race condition*/ }; /* driver private database */ struct modphy_private { struct platform_device *pdev; /* platform device */ struct mrpc_client *mrpc; /* mrpc client handle */ }; static struct modphy_private *this; struct modphy_args { unsigned int client; unsigned int lane; }; enum { MODPHY_INIT_PROC = 0, MODPHY_LANE_CONFIGURE_PROC, MODPHY_TX_DISABLE_PROC, MODPHY_RESET_K_ALIGN_PROC, MODPHY_RESET_CLIENT_PROC, MODPHY_ASSERT_RESET_PROC, }; static inline int modphy_mrpc_call(__u8 procid, ModphyLanes_e lane, ModphyController_e modphyClientId) { struct modphy_private *priv = this; struct modphy_args args; int ret, errcode; if (!priv) { pr_err("ERROR: mrpc modphy not initialized"); return MODPHY_FAIL; } args.client = htonl(modphyClientId); args.lane = htonl(lane); ret = mrpc_call(priv->mrpc, procid, &args, sizeof(struct modphy_args), NULL, 0, MODPHY_CLIENT_MRPC_CALL_TIMEOUT_MS, &errcode); if (ret || errcode) { pr_err("ERROR: ret=%d, errcode=%d", ret, errcode); return MODPHY_FAIL; } return MODPHY_OK; } /*********************************************************************************************************/ /*! \fn int modphy_assert_reset_client(ModphyController_e modphyClientId) ********************************************************************************************************* * \brief This function inits the modphy for a specific client. * \param[in] modphyClientId : Client ID * \return MODPHY_OK if writing succeed otherwise MODPHY_FAIL ********************************************************************************************************/ int modphy_assert_reset_client(ModphyController_e modphyClientId) { int res; res = modphy_mrpc_call(MODPHY_ASSERT_RESET_PROC, -1, modphyClientId); return res; } EXPORT_SYMBOL(modphy_assert_reset_client); /*********************************************************************************************************/ /*! \fn int modphy_init(ModphyController_e modphyClientId) ********************************************************************************************************* * \brief This function inits the modphy for a specific client. * \param[in] modphyClientId : Client ID * \return MODPHY_OK if writing succeed otherwise MODPHY_FAIL ********************************************************************************************************/ int modphy_init(ModphyController_e modphyClientId) { return modphy_mrpc_call(MODPHY_INIT_PROC, -1, modphyClientId); } EXPORT_SYMBOL(modphy_init); /*********************************************************************************************************/ /*! \fn int modphy_configure(ModphyController_e modphyClientId) ********************************************************************************************************* * \brief This function configure the modphy for a specific client. * \param[in] modphyClientId : Client ID * \return MODPHY_OK if writing succeed otherwise MODPHY_FAIL ********************************************************************************************************/ int modphy_lane_configure(ModphyLanes_e lane, ModphyController_e modphyClientId) { return modphy_mrpc_call(MODPHY_LANE_CONFIGURE_PROC, lane, modphyClientId); } EXPORT_SYMBOL(modphy_lane_configure); /*********************************************************************************************************/ /*! \fn int modphy_tx_disable(ModphyLanes_e lane, ModphyController_e modphyClientId) ********************************************************************************************************* * \brief This function disable TX lanes * \param[in] modphyClientId : Client ID * \return MODPHY_OK if writing succeed otherwise MODPHY_FAIL ********************************************************************************************************/ int modphy_tx_disable(ModphyLanes_e lane, ModphyController_e modphyClientId) { return modphy_mrpc_call(MODPHY_TX_DISABLE_PROC, lane, modphyClientId); } EXPORT_SYMBOL(modphy_tx_disable); /*********************************************************************************************************/ /*! \fn int modphy_reset_k_align(ModphyController_e modphyClientId) ********************************************************************************************************* * \brief This function reset K alignment in modphy * \param[in] modphyClientId : Client ID * \return MODPHY_OK if writing succeed otherwise MODPHY_FAIL ********************************************************************************************************/ int modphy_reset_k_align(ModphyController_e modphyClientId) { return modphy_mrpc_call(MODPHY_RESET_K_ALIGN_PROC, -1, modphyClientId); } EXPORT_SYMBOL(modphy_reset_k_align); /*********************************************************************************************************/ /*! \fn void modphy_reset_client(ModphyController_e modphyClientId) ********************************************************************************************************* * \brief This function resets ModPhy client. Used for ext.switches WA. Can be scheduled out. * \param[in] modphyClientId : Client ID * \return void ********************************************************************************************************/ void modphy_reset_client(ModphyController_e modphyClientId) { modphy_mrpc_call(MODPHY_RESET_CLIENT_PROC, -1, modphyClientId); } EXPORT_SYMBOL(modphy_reset_client); /* sysfs for future use */ static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf) { struct modphy_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 *modphy_attrs[] = { &dev_attr_status.attr, NULL }; static struct attribute_group modphy_attrs_group = { .attrs = modphy_attrs, }; /** * modphy_mrpc_probe * * @param pdev platform device * * @return 0 for success, error code otherwise */ static int modphy_mrpc_probe(struct platform_device *pdev) { struct modphy_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, &modphy_attrs_group); if (ret) { pr_err("sysfs_create_group failed (ret=%d)", ret); return ret; } priv->mrpc = mrpc_client_register(MRPC_RESERVED_ID_MODPHY, "modphy"); if (!priv->mrpc) { pr_err("failed to register modphy"); ret = -ENODEV; goto out_remove_group; } this = priv; return 0; out_remove_group: sysfs_remove_group(&priv->pdev->dev.kobj, &modphy_attrs_group); return ret; } /** * modphy_mrpc_remove * * This function is called when the modphy mrpc driver is * removed. * * @param pdev platform device * * @return 0 for success, error code otherwise */ static int modphy_mrpc_remove(struct platform_device *pdev) { struct modphy_private *priv = platform_get_drvdata(pdev); mrpc_client_unregister(priv->mrpc); sysfs_remove_group(&priv->pdev->dev.kobj, &modphy_attrs_group); dev_set_drvdata(&pdev->dev, NULL); this = NULL; return 0; } static struct platform_driver modphy_driver = { .driver = { .name = "modphy", }, .probe = modphy_mrpc_probe, .remove = modphy_mrpc_remove, }; static struct platform_device *modphy_device; static int create_proxy_device(void) { int ret; struct device *dev_ret; if ((ret = alloc_chrdev_region(&dev, 0, 1, MODPHY_NAME)) < 0) { pr_err("MODPHY: Failed alloc_chrdev_region\n"); return ret; } pr_debug("MODPHY: alloc_chrdev_region - Done\n"); if (!(c_dev = cdev_alloc())) { pr_err("%s:%d Failed to allocate character device modphy_driver\n", __FUNCTION__, __LINE__); unregister_chrdev_region(dev, MINOR_CNT); return (-1); } pr_debug("MODPHY: cdev_alloc - Done\n"); /* Init the driver */ cdev_init(c_dev, &modphy_drv_fops); pr_debug("MODPHY: cdev_init - Done\n"); if ((ret = cdev_add(c_dev, dev, MINOR_CNT)) < 0) { pr_err("%s:%d Failed to add character device modphy_driver\n", __FUNCTION__, __LINE__); cdev_del(c_dev); unregister_chrdev_region(dev, MINOR_CNT); return ret; } pr_debug("MODPHY: cdev_add - Done\n"); if (IS_ERR(cl = class_create(THIS_MODULE, MODPHY_NAME))) { pr_err("MODPHY: class_create -Fail\n"); cdev_del(c_dev); unregister_chrdev_region(dev, MINOR_CNT); return PTR_ERR(cl); } pr_debug("MODPHY: class_create - Done\n"); if (IS_ERR(dev_ret = device_create(cl, NULL, dev, NULL, MODPHY_NAME))) { class_destroy(cl); cdev_del(c_dev); unregister_chrdev_region(dev, MINOR_CNT); return PTR_ERR(dev_ret); } return 0; } static int modphy_mrpc_init(void) { int ret; ret = platform_driver_register(&modphy_driver); if (ret < 0) { pr_err("Failed to register modphy platform driver: %d\n", ret); return ret; } pr_debug("modphy client platform driver is registered"); modphy_device = platform_device_register_simple("modphy", -1, NULL, 0); if (IS_ERR(modphy_device)) { pr_err("Failed to register modphy platform device\n"); platform_driver_unregister(&modphy_driver); return PTR_ERR(modphy_device); } dev_info(&modphy_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_destroy(cl, dev); class_destroy(cl); cdev_del(c_dev); unregister_chrdev_region(dev, MINOR_CNT); } static void __exit modphy_mrpc_exit(void) { platform_device_unregister(modphy_device); platform_driver_unregister(&modphy_driver); delete_proxy_device(); } static long modphy_proxy_ioctl(struct file *fd, unsigned int cmd, unsigned long arg) { int ret = MODPHY_OK; ModphyController_e modphyClientId; if (fd == NULL) { pr_err("\nMODPHY Error value\n"); return MODPHY_FAIL; } switch (cmd) { case MODPHY_RESET_LANE: { if (copy_from_user(&modphyClientId, (void __user *)arg, sizeof(modphyClientId))) { pr_err("\n MODPHY failed to copy from user data. IOCTL=%d\n", cmd); return -EFAULT; } modphy_reset_client(modphyClientId); break; } default: pr_err("%s:%d Invalid IOCTL(0x%08X) has been received \n", __FUNCTION__, __LINE__, cmd); return -ENOSYS; } return ret; } /******************************************************/ /** Module Declarations **/ /******************************************************/ static_notifier_module_init(mrpc_ready, modphy_mrpc_init); module_exit(modphy_mrpc_exit); MODULE_AUTHOR("Intel Corporation"); MODULE_AUTHOR("Tomer Eliyahu tomer.b.eliyahu@intel.com"); MODULE_DESCRIPTION("modphy mrpc client"); MODULE_LICENSE("GPL"); MODULE_VERSION("1.0");