4618441536
Current Intel SOC cores use a MailBox Interface (MBI) to provide access to configuration registers on devices (called units) connected to the system fabric. This is a support driver that implements access to this interface on those platforms that can enumerate the device using PCI. Initial support is for BayTrail, for which port definitons are provided. This is a requirement for implementing platform specific features (e.g. RAPL driver requires this to perform platform specific power management using the registers in PUNIT). Dependant modules should select IOSF_MBI in their respective Kconfig configuraiton. Serialized access is handled by all exported routines with spinlocks. The API includes 3 functions for access to unit registers: int iosf_mbi_read(u8 port, u8 opcode, u32 offset, u32 *mdr) int iosf_mbi_write(u8 port, u8 opcode, u32 offset, u32 mdr) int iosf_mbi_modify(u8 port, u8 opcode, u32 offset, u32 mdr, u32 mask) port: indicating the unit being accessed opcode: the read or write port specific opcode offset: the register offset within the port mdr: the register data to be read, written, or modified mask: bit locations in mdr to change Returns nonzero on error Note: GPU code handles access to the GFX unit. Therefore access to that unit with this driver is disallowed to avoid conflicts. Signed-off-by: David E. Box <david.e.box@linux.intel.com> Link: http://lkml.kernel.org/r/1389216471-734-1-git-send-email-david.e.box@linux.intel.com Signed-off-by: H. Peter Anvin <hpa@linux.intel.com> Cc: Rafael J. Wysocki <rjw@rjwysocki.net> Cc: Matthew Garrett <mjg59@srcf.ucam.org>
226 lines
5 KiB
C
226 lines
5 KiB
C
/*
|
|
* IOSF-SB MailBox Interface Driver
|
|
* Copyright (c) 2013, Intel Corporation.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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.
|
|
*
|
|
*
|
|
* The IOSF-SB is a fabric bus available on Atom based SOC's that uses a
|
|
* mailbox interface (MBI) to communicate with mutiple devices. This
|
|
* driver implements access to this interface for those platforms that can
|
|
* enumerate the device using PCI.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/pci.h>
|
|
|
|
#include <asm/iosf_mbi.h>
|
|
|
|
static DEFINE_SPINLOCK(iosf_mbi_lock);
|
|
|
|
static inline u32 iosf_mbi_form_mcr(u8 op, u8 port, u8 offset)
|
|
{
|
|
return (op << 24) | (port << 16) | (offset << 8) | MBI_ENABLE;
|
|
}
|
|
|
|
static struct pci_dev *mbi_pdev; /* one mbi device */
|
|
|
|
static int iosf_mbi_pci_read_mdr(u32 mcrx, u32 mcr, u32 *mdr)
|
|
{
|
|
int result;
|
|
|
|
if (!mbi_pdev)
|
|
return -ENODEV;
|
|
|
|
if (mcrx) {
|
|
result = pci_write_config_dword(mbi_pdev, MBI_MCRX_OFFSET,
|
|
mcrx);
|
|
if (result < 0)
|
|
goto fail_read;
|
|
}
|
|
|
|
result = pci_write_config_dword(mbi_pdev, MBI_MCR_OFFSET, mcr);
|
|
if (result < 0)
|
|
goto fail_read;
|
|
|
|
result = pci_read_config_dword(mbi_pdev, MBI_MDR_OFFSET, mdr);
|
|
if (result < 0)
|
|
goto fail_read;
|
|
|
|
return 0;
|
|
|
|
fail_read:
|
|
dev_err(&mbi_pdev->dev, "PCI config access failed with %d\n", result);
|
|
return result;
|
|
}
|
|
|
|
static int iosf_mbi_pci_write_mdr(u32 mcrx, u32 mcr, u32 mdr)
|
|
{
|
|
int result;
|
|
|
|
if (!mbi_pdev)
|
|
return -ENODEV;
|
|
|
|
result = pci_write_config_dword(mbi_pdev, MBI_MDR_OFFSET, mdr);
|
|
if (result < 0)
|
|
goto fail_write;
|
|
|
|
if (mcrx) {
|
|
result = pci_write_config_dword(mbi_pdev, MBI_MCRX_OFFSET,
|
|
mcrx);
|
|
if (result < 0)
|
|
goto fail_write;
|
|
}
|
|
|
|
result = pci_write_config_dword(mbi_pdev, MBI_MCR_OFFSET, mcr);
|
|
if (result < 0)
|
|
goto fail_write;
|
|
|
|
return 0;
|
|
|
|
fail_write:
|
|
dev_err(&mbi_pdev->dev, "PCI config access failed with %d\n", result);
|
|
return result;
|
|
}
|
|
|
|
int iosf_mbi_read(u8 port, u8 opcode, u32 offset, u32 *mdr)
|
|
{
|
|
u32 mcr, mcrx;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
/*Access to the GFX unit is handled by GPU code */
|
|
if (port == BT_MBI_UNIT_GFX) {
|
|
WARN_ON(1);
|
|
return -EPERM;
|
|
}
|
|
|
|
mcr = iosf_mbi_form_mcr(opcode, port, offset & MBI_MASK_LO);
|
|
mcrx = offset & MBI_MASK_HI;
|
|
|
|
spin_lock_irqsave(&iosf_mbi_lock, flags);
|
|
ret = iosf_mbi_pci_read_mdr(mcrx, mcr, mdr);
|
|
spin_unlock_irqrestore(&iosf_mbi_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(iosf_mbi_read);
|
|
|
|
int iosf_mbi_write(u8 port, u8 opcode, u32 offset, u32 mdr)
|
|
{
|
|
u32 mcr, mcrx;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
/*Access to the GFX unit is handled by GPU code */
|
|
if (port == BT_MBI_UNIT_GFX) {
|
|
WARN_ON(1);
|
|
return -EPERM;
|
|
}
|
|
|
|
mcr = iosf_mbi_form_mcr(opcode, port, offset & MBI_MASK_LO);
|
|
mcrx = offset & MBI_MASK_HI;
|
|
|
|
spin_lock_irqsave(&iosf_mbi_lock, flags);
|
|
ret = iosf_mbi_pci_write_mdr(mcrx, mcr, mdr);
|
|
spin_unlock_irqrestore(&iosf_mbi_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(iosf_mbi_write);
|
|
|
|
int iosf_mbi_modify(u8 port, u8 opcode, u32 offset, u32 mdr, u32 mask)
|
|
{
|
|
u32 mcr, mcrx;
|
|
u32 value;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
/*Access to the GFX unit is handled by GPU code */
|
|
if (port == BT_MBI_UNIT_GFX) {
|
|
WARN_ON(1);
|
|
return -EPERM;
|
|
}
|
|
|
|
mcr = iosf_mbi_form_mcr(opcode, port, offset & MBI_MASK_LO);
|
|
mcrx = offset & MBI_MASK_HI;
|
|
|
|
spin_lock_irqsave(&iosf_mbi_lock, flags);
|
|
|
|
/* Read current mdr value */
|
|
ret = iosf_mbi_pci_read_mdr(mcrx, mcr & MBI_RD_MASK, &value);
|
|
if (ret < 0) {
|
|
spin_unlock_irqrestore(&iosf_mbi_lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
/* Apply mask */
|
|
value &= ~mask;
|
|
mdr &= mask;
|
|
value |= mdr;
|
|
|
|
/* Write back */
|
|
ret = iosf_mbi_pci_write_mdr(mcrx, mcr | MBI_WR_MASK, value);
|
|
|
|
spin_unlock_irqrestore(&iosf_mbi_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(iosf_mbi_modify);
|
|
|
|
static int iosf_mbi_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *unused)
|
|
{
|
|
int ret;
|
|
|
|
ret = pci_enable_device(pdev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "error: could not enable device\n");
|
|
return ret;
|
|
}
|
|
|
|
mbi_pdev = pci_dev_get(pdev);
|
|
return 0;
|
|
}
|
|
|
|
static DEFINE_PCI_DEVICE_TABLE(iosf_mbi_pci_ids) = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0F00) },
|
|
{ 0, },
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, iosf_mbi_pci_ids);
|
|
|
|
static struct pci_driver iosf_mbi_pci_driver = {
|
|
.name = "iosf_mbi_pci",
|
|
.probe = iosf_mbi_probe,
|
|
.id_table = iosf_mbi_pci_ids,
|
|
};
|
|
|
|
static int __init iosf_mbi_init(void)
|
|
{
|
|
return pci_register_driver(&iosf_mbi_pci_driver);
|
|
}
|
|
|
|
static void __exit iosf_mbi_exit(void)
|
|
{
|
|
pci_unregister_driver(&iosf_mbi_pci_driver);
|
|
if (mbi_pdev) {
|
|
pci_dev_put(mbi_pdev);
|
|
mbi_pdev = NULL;
|
|
}
|
|
}
|
|
|
|
module_init(iosf_mbi_init);
|
|
module_exit(iosf_mbi_exit);
|
|
|
|
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
|
|
MODULE_DESCRIPTION("IOSF Mailbox Interface accessor");
|
|
MODULE_LICENSE("GPL v2");
|