/* * xgmii-power-control.c * * GPL LICENSE SUMMARY * * Copyright(c) 2016-2018 Intel Corporation. All rights reserved. * * 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 * */ #include #include #include #include #include #include #include #include #include #include #include #include #define DWC_ETH_QOS_MAX_NUM_NETDEV (5) #define HW_MBOX_LAN_MSG_TAG (5) #define PCE_MSG_OPCODE (6) /*PCE response timeout in milli seconds */ #define PCE_RSP_TIMEOUT (60*1000) enum xgmii_power_state { XGMII_PWR_STATE_OFF = 0, XGMII_PWR_STATE_ACTIVE = 1, }; enum pce_response { PCE_RSP_OK = 0, PCE_RSP_NOK = 1, PCE_RSP_UNKNOWN = 2, }; enum pce_cmd { PCE_CMD_ALLOC = 0, PCE_CMD_FREE = 1, }; enum rsc_id { RSC_INVALID = 0, RSC_MOCA_PRIMARY = 1, RSC_MOCA_SECONDARY = 2, RSC_SGMII0 = 3, RSC_SGMII1 = 4, RSC_RGMII2 = 5, RSC_RGMII3 = 6, }; struct xgmii_pce_cmd_msg { uint32_t opcode; uint32_t cmd; uint32_t rsc_id; }; struct xgmii_pce_response_msg { uint32_t opcode; uint32_t response; }; /* SGMII0 connected to GMAC 0 * SGMII0 connected to GMAC 1 * SGMII0 connected to GMAC 2 * SGMII0 connected to GMAC 3 * No interface connected to GMAC 4 */ struct xgmii_interface_data { struct device *dev; int uid; int power_state; int reset_gpio; char *gpio_label; }; /** Global Variables */ static bool g_registered_pce_rsp_msg_cb; static uint32_t g_pce_rsp_status = PCE_RSP_UNKNOWN; static struct mutex g_pce_msg_lock; static DECLARE_WAIT_QUEUE_HEAD(xgmii_wq); /***********************************************/ static int xgmii_runtime_suspend(struct device *dev); static int xgmii_runtime_resume(struct device *dev); static const struct dev_pm_ops xgmii_pm_ops = { SET_RUNTIME_PM_OPS(xgmii_runtime_suspend, xgmii_runtime_resume, NULL) }; static inline enum rsc_id to_rsc_id(int uid) { enum rsc_id id = RSC_INVALID; if (uid == 0) id = RSC_SGMII0; else if (uid == 1) id = RSC_SGMII1; else if (uid == 2) id = RSC_RGMII2; else if (uid == 3) id = RSC_RGMII3; return id; } static ssize_t xgmii_pwr_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t ret = 0; char *ptr = buf; ptr += scnprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&dev->power.usage_count)); ret = ptr - buf; return ret; } static ssize_t xgmii_pwr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long attribute_value; ssize_t ret; ret = kstrtoul(buf, 0, &attribute_value); if (ret != 0) return ret; if ((attribute_value == 1) && (!atomic_read(&dev->power.usage_count))) pm_runtime_get_sync(dev); else if ((attribute_value == 0) && (atomic_read(&dev->power.usage_count) == 1)) pm_runtime_put_sync(dev); else ret = -EINVAL; if (!ret) ret = count; return ret; } static DEVICE_ATTR(sgmii0_pwr_ctrl, S_IRUGO|S_IWUSR, xgmii_pwr_show, xgmii_pwr_store); static DEVICE_ATTR(sgmii1_pwr_ctrl, S_IRUGO|S_IWUSR, xgmii_pwr_show, xgmii_pwr_store); static DEVICE_ATTR(rgmii2_pwr_ctrl, S_IRUGO|S_IWUSR, xgmii_pwr_show, xgmii_pwr_store); static DEVICE_ATTR(rgmii3_pwr_ctrl, S_IRUGO|S_IWUSR, xgmii_pwr_show, xgmii_pwr_store); static ssize_t xgmii_reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long attribute_value; int gpio_val = -1; ssize_t ret; struct platform_device *pdev = to_platform_device(dev); struct xgmii_interface_data *priv = platform_get_drvdata(pdev); if (!gpio_is_valid(priv->reset_gpio)) return -ENODEV; ret = kstrtoul(buf, 0, &attribute_value); if (ret != 0) return ret; if (attribute_value) gpio_val = 1; else gpio_val = 0; gpio_set_value(priv->reset_gpio, gpio_val); return count; } static DEVICE_ATTR(sgmii0_reset, S_IWUSR, NULL, xgmii_reset_store); static DEVICE_ATTR(sgmii1_reset, S_IWUSR, NULL, xgmii_reset_store); static DEVICE_ATTR(rgmii2_reset, S_IWUSR, NULL, xgmii_reset_store); static DEVICE_ATTR(rgmii3_reset, S_IWUSR, NULL, xgmii_reset_store); static int create_sysfs_attrs(struct xgmii_interface_data *priv) { int ret = 0; struct device_attribute *pwr_ctrl_attr = NULL; struct device_attribute *reset_attr = NULL; priv->gpio_label = NULL; if (priv->uid == 0) { pwr_ctrl_attr = &dev_attr_sgmii0_pwr_ctrl; reset_attr = &dev_attr_sgmii0_reset; priv->gpio_label = "sgmii0_reset"; } else if (priv->uid == 1) { pwr_ctrl_attr = &dev_attr_sgmii1_pwr_ctrl; reset_attr = &dev_attr_sgmii1_reset; priv->gpio_label = "sgmii1_reset"; } else if (priv->uid == 2) { pwr_ctrl_attr = &dev_attr_rgmii2_pwr_ctrl; reset_attr = &dev_attr_rgmii2_reset; priv->gpio_label = "rgmii2_reset"; } else if (priv->uid == 3) { pwr_ctrl_attr = &dev_attr_rgmii3_pwr_ctrl; reset_attr = &dev_attr_rgmii3_reset; priv->gpio_label = "rgmii3_reset"; } if (pwr_ctrl_attr) { ret = device_create_file(priv->dev, pwr_ctrl_attr); if (ret < 0) return ret; } if ((priv->reset_gpio > 0) && reset_attr) ret = device_create_file(priv->dev, reset_attr); return ret; } static void remove_sysfs_attrs(struct xgmii_interface_data *priv) { struct device_attribute *pwr_ctrl_attr = NULL; struct device_attribute *reset_attr = NULL; if (priv->uid == 0) { pwr_ctrl_attr = &dev_attr_sgmii0_pwr_ctrl; reset_attr = &dev_attr_sgmii0_reset; } else if (priv->uid == 1) { pwr_ctrl_attr = &dev_attr_sgmii1_pwr_ctrl; reset_attr = &dev_attr_sgmii1_reset; } else if (priv->uid == 2) { pwr_ctrl_attr = &dev_attr_rgmii2_pwr_ctrl; reset_attr = &dev_attr_rgmii2_reset; } else if (priv->uid == 3) { pwr_ctrl_attr = &dev_attr_rgmii3_pwr_ctrl; reset_attr = &dev_attr_rgmii3_reset; } if (pwr_ctrl_attr) device_remove_file(priv->dev, pwr_ctrl_attr); if ((priv->reset_gpio > 0) && reset_attr) device_remove_file(priv->dev, reset_attr); return; } static int xgmii_pce_rsp_msg_cb(hw_mbox_Masters_e commander, Uint8 *dataBuf, Uint32 dataLen, Uint32 *token) { struct xgmii_pce_response_msg *msg; msg = (struct xgmii_pce_response_msg *) dataBuf; if (msg->opcode != PCE_MSG_OPCODE) { pr_err("Invalid Opcode %d in pce response\n", msg->opcode); return -1; } /* Acknowledge the PCE response */ hwMbox_sendAckOpcode(HW_MBOX_MASTER_NP_CPU); g_pce_rsp_status = msg->response; wake_up(&xgmii_wq); pr_debug("pce reply msg cb rsp %d\n", g_pce_rsp_status); return 0; } static int xgmii_send_pce_command(enum pce_cmd cmd, enum rsc_id id) { int ret; uint32_t reply_len; struct xgmii_pce_cmd_msg *msg; mutex_lock(&g_pce_msg_lock); msg = kzalloc(sizeof(struct xgmii_pce_cmd_msg), GFP_KERNEL); if (!msg) { ret = -ENOMEM; pr_err("xGMII PCE send cmd msg alloc failed ret %d\n", ret); goto out; } msg->opcode = PCE_MSG_OPCODE; msg->cmd = cmd; msg->rsc_id = id; pr_debug("opcode %d cmd %d id %d\n", PCE_MSG_OPCODE, cmd, id); g_pce_rsp_status = PCE_RSP_UNKNOWN; ret = hwMbox_sendOpcode(HW_MBOX_MASTER_NP_CPU, HW_MBOX_LAN_MSG_TAG, (uint8_t *)msg, sizeof(struct xgmii_pce_cmd_msg), sizeof(struct xgmii_pce_cmd_msg), &reply_len); if (ret) { pr_err("xGMII PCE send cmd failed ret %d\n", ret); goto out; } ret = wait_event_timeout(xgmii_wq, (g_pce_rsp_status != PCE_RSP_UNKNOWN), msecs_to_jiffies(PCE_RSP_TIMEOUT)); if (!ret) { ret = -EBUSY; pr_err("xGMII PCE response 60 sec timeout ret %d\n", ret); } else { ret = g_pce_rsp_status; if (ret) pr_err("xGMII PCE response NOK ret %d\n", ret); } out: kfree(msg); mutex_unlock(&g_pce_msg_lock); return ret; } int xgmii_runtime_suspend(struct device *dev) { int ret = 0; struct platform_device *pdev = to_platform_device(dev); struct xgmii_interface_data *priv = platform_get_drvdata(pdev); uint32_t rsc_id = to_rsc_id(priv->uid); dev_dbg(dev, "xGMII suspend uid %d\n", priv->uid); if (priv->power_state != XGMII_PWR_STATE_OFF) { xgmii_send_pce_command(PCE_CMD_FREE, rsc_id); priv->power_state = XGMII_PWR_STATE_OFF; } else { dev_info(dev, "xGMII uid %d is already in OFF state\n", priv->uid); } return ret; } int xgmii_runtime_resume(struct device *dev) { int ret = 0; struct platform_device *pdev = to_platform_device(dev); struct xgmii_interface_data *priv = platform_get_drvdata(pdev); uint32_t rsc_id = to_rsc_id(priv->uid); dev_dbg(dev, "xGMII resume uid %d\n", priv->uid); if (priv->power_state != XGMII_PWR_STATE_ACTIVE) { xgmii_send_pce_command(PCE_CMD_ALLOC, rsc_id); priv->power_state = XGMII_PWR_STATE_ACTIVE; } else { dev_info(dev, "xGMII uid %d is already in ACTIVE state\n", priv->uid); } return ret; } static int xgmii_pwr_ctrl_request_gpio(struct device *dev, int uid) { struct gpio_desc *desc; if (uid == 0) { int gpio = avm_gpio_find_by_name("gpio_avm_sgmii0_rst", NULL); int ret; if (gpio_is_valid(gpio)) { ret = devm_gpio_request_one(dev, gpio, GPIOF_DIR_OUT|GPIOF_INIT_LOW, dev_name(dev)); return ret ?: gpio; } } desc = devm_gpiod_get_index(dev, NULL, 1, 0); if (IS_ERR(desc)) return PTR_ERR(desc); return desc_to_gpio(desc); } static int xgmii_pwr_ctrl_probe(struct platform_device *pdev) { int ret = 0; int uid = -1; struct device *dev = &pdev->dev; struct xgmii_interface_data *priv; acpi_handle handle = ACPI_HANDLE(&pdev->dev); struct acpi_device_info *info = NULL; acpi_status status; status = acpi_get_object_info(handle, &info); if (ACPI_FAILURE(status) || !(info->valid & ACPI_VALID_UID)) { dev_err(dev, "Missing ACPI info: No uid\n"); return -ENODEV; } ret = kstrtoint(info->unique_id.string, 0, &uid); if (ret) { dev_err(dev, "Failed converting UID to int ret=[%d]\n", ret); return ret; } if (0 > uid || DWC_ETH_QOS_MAX_NUM_NETDEV < uid) { dev_err(dev, "Bad ACPI info: uid=%d\n", uid); return -ENODEV; } if (uid == 4) { dev_info(dev, "No xGMII power control for GMAC 5\n"); return 0; } priv = devm_kzalloc(dev, sizeof(struct xgmii_interface_data), GFP_KERNEL); if (!priv) { dev_err(dev, "ERROR, NOMEM for priv data UID %d\n", uid); return -ENOMEM; } priv->dev = &pdev->dev; priv->uid = uid; priv->reset_gpio = xgmii_pwr_ctrl_request_gpio(dev, uid); if (!gpio_is_valid(priv->reset_gpio) && (ret = priv->reset_gpio) != -ENOENT) { dev_err(dev, "Error requesting reset gpio: %d\n", ret); return ret; } dev_info(dev, "Reset gpio: %d\n", priv->reset_gpio); ret = create_sysfs_attrs(priv); if (ret) { dev_err(dev, "Failed to create sysfs attr uid %d\n", uid); return ret; } platform_set_drvdata(pdev, priv); if (!g_registered_pce_rsp_msg_cb) { ret = hwMbox_registerRecvOpcode(HW_MBOX_MASTER_NP_CPU, xgmii_pce_rsp_msg_cb, HW_MBOX_LAN_MSG_TAG, 0); if (ret) { dev_err(dev, "Failed to register pce reply msg callback ret = %d\n", ret); goto err_remove_sysfs_attrs; } g_registered_pce_rsp_msg_cb = true; } /* NetIP bring xGMII interfaces in ON state. If NetIP keep them * in OFF state, pm_runtime_set_active can be deleted */ priv->power_state = XGMII_PWR_STATE_ACTIVE; pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); dev_info(dev, "\nxGMII uid %d probed ret %d\n", uid, ret); return 0; err_remove_sysfs_attrs: remove_sysfs_attrs(priv); return ret; } static int xgmii_pwr_ctrl_remove(struct platform_device *pdev) { struct xgmii_interface_data *priv = platform_get_drvdata(pdev); pm_runtime_disable(&pdev->dev); remove_sysfs_attrs(priv); dev_info(&pdev->dev, "xGMII uid %d removed\n", priv->uid); return 0; } static const struct acpi_device_id intelce2700_xgmii_acpi_match[] = { { "INT351B", 0 }, { } }; MODULE_DEVICE_TABLE(acpi, intelce2700_xgmii_acpi_match); static struct platform_driver xgmii_pwr_ctrl_driver = { .probe = xgmii_pwr_ctrl_probe, .remove = xgmii_pwr_ctrl_remove, .driver = { .name = "intelce2700_xgmii_pwr_ctrl_driver", .owner = THIS_MODULE, .acpi_match_table = ACPI_PTR(intelce2700_xgmii_acpi_match), .pm = &xgmii_pm_ops, } }; static int __init xgmii_pwr_ctrl_init(void) { mutex_init(&g_pce_msg_lock); return platform_driver_register(&xgmii_pwr_ctrl_driver); } static void xgmii_pwr_ctrl_exit(void) { mutex_destroy(&g_pce_msg_lock); platform_driver_unregister(&xgmii_pwr_ctrl_driver); } module_init(xgmii_pwr_ctrl_init); module_exit(xgmii_pwr_ctrl_exit); MODULE_DESCRIPTION("Intel(R) CE 2700 xGMII Power Control Driver"); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL");