// SPDX-License-Identifier: GPL-2.0 /* * Intel Speed Select Interface: Mbox via PCI Interface * Copyright (c) 2019, Intel Corporation. * All rights reserved. * * Author: Srinivas Pandruvada */ #include #include #include #include #include #include #include "isst_if_common.h" #define PUNIT_MAILBOX_DATA 0xA0 #define PUNIT_MAILBOX_INTERFACE 0xA4 #define PUNIT_MAILBOX_BUSY_BIT 31 /* * Commands has variable amount of processing time. Most of the commands will * be done in 0-3 tries, but some takes up to 50. * The real processing time was observed as 25us for the most of the commands * at 2GHz. It is possible to optimize this count taking samples on customer * systems. */ #define OS_MAILBOX_RETRY_COUNT 50 struct isst_if_device { struct mutex mutex; }; static int isst_if_mbox_cmd(struct pci_dev *pdev, struct isst_if_mbox_cmd *mbox_cmd) { u32 retries, data; int ret; /* Poll for rb bit == 0 */ retries = OS_MAILBOX_RETRY_COUNT; do { ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, &data); if (ret) return ret; if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { ret = -EBUSY; continue; } ret = 0; break; } while (--retries); if (ret) return ret; /* Write DATA register */ ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_DATA, mbox_cmd->req_data); if (ret) return ret; /* Write command register */ data = BIT_ULL(PUNIT_MAILBOX_BUSY_BIT) | (mbox_cmd->parameter & GENMASK_ULL(13, 0)) << 16 | (mbox_cmd->sub_command << 8) | mbox_cmd->command; ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, data); if (ret) return ret; /* Poll for rb bit == 0 */ retries = OS_MAILBOX_RETRY_COUNT; do { ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, &data); if (ret) return ret; if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { ret = -EBUSY; continue; } if (data & 0xff) return -ENXIO; ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_DATA, &data); if (ret) return ret; mbox_cmd->resp_data = data; ret = 0; break; } while (--retries); return ret; } static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) { struct isst_if_mbox_cmd *mbox_cmd; struct isst_if_device *punit_dev; struct pci_dev *pdev; int ret; mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; if (isst_if_mbox_cmd_invalid(mbox_cmd)) return -EINVAL; if (isst_if_mbox_cmd_set_req(mbox_cmd) && !capable(CAP_SYS_ADMIN)) return -EPERM; pdev = isst_if_get_pci_dev(mbox_cmd->logical_cpu, 1, 30, 1); if (!pdev) return -EINVAL; punit_dev = pci_get_drvdata(pdev); if (!punit_dev) return -EINVAL; /* * Basically we are allowing one complete mailbox transaction on * a mapped PCI device at a time. */ mutex_lock(&punit_dev->mutex); ret = isst_if_mbox_cmd(pdev, mbox_cmd); if (!ret && !resume && isst_if_mbox_cmd_set_req(mbox_cmd)) ret = isst_store_cmd(mbox_cmd->command, mbox_cmd->sub_command, mbox_cmd->logical_cpu, 1, mbox_cmd->parameter, mbox_cmd->req_data); mutex_unlock(&punit_dev->mutex); if (ret) return ret; *write_only = 0; return 0; } static const struct pci_device_id isst_if_mbox_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_0)}, { 0 }, }; MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids); static int isst_if_mbox_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct isst_if_device *punit_dev; struct isst_if_cmd_cb cb; int ret; punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL); if (!punit_dev) return -ENOMEM; ret = pcim_enable_device(pdev); if (ret) return ret; mutex_init(&punit_dev->mutex); pci_set_drvdata(pdev, punit_dev); memset(&cb, 0, sizeof(cb)); cb.cmd_size = sizeof(struct isst_if_mbox_cmd); cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); cb.cmd_callback = isst_if_mbox_proc_cmd; cb.owner = THIS_MODULE; ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); if (ret) mutex_destroy(&punit_dev->mutex); return ret; } static void isst_if_mbox_remove(struct pci_dev *pdev) { struct isst_if_device *punit_dev; punit_dev = pci_get_drvdata(pdev); isst_if_cdev_unregister(ISST_IF_DEV_MBOX); mutex_destroy(&punit_dev->mutex); } static int __maybe_unused isst_if_resume(struct device *device) { isst_resume_common(); return 0; } static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, NULL, isst_if_resume); static struct pci_driver isst_if_pci_driver = { .name = "isst_if_mbox_pci", .id_table = isst_if_mbox_ids, .probe = isst_if_mbox_probe, .remove = isst_if_mbox_remove, .driver.pm = &isst_if_pm_ops, }; module_pci_driver(isst_if_pci_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel speed select interface pci mailbox driver");