/* 
 | 
 * 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 <linux/debugfs.h> 
 | 
#include <linux/capability.h> 
 | 
  
 | 
#include <asm/iosf_mbi.h> 
 | 
  
 | 
#define PCI_DEVICE_ID_BAYTRAIL        0x0F00 
 | 
#define PCI_DEVICE_ID_BRASWELL        0x2280 
 | 
#define PCI_DEVICE_ID_QUARK_X1000    0x0958 
 | 
#define PCI_DEVICE_ID_TANGIER        0x1170 
 | 
  
 | 
static struct pci_dev *mbi_pdev; 
 | 
static DEFINE_SPINLOCK(iosf_mbi_lock); 
 | 
static DEFINE_MUTEX(iosf_mbi_punit_mutex); 
 | 
static BLOCKING_NOTIFIER_HEAD(iosf_mbi_pmic_bus_access_notifier); 
 | 
  
 | 
static inline u32 iosf_mbi_form_mcr(u8 op, u8 port, u8 offset) 
 | 
{ 
 | 
    return (op << 24) | (port << 16) | (offset << 8) | MBI_ENABLE; 
 | 
} 
 | 
  
 | 
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); 
 | 
  
 | 
bool iosf_mbi_available(void) 
 | 
{ 
 | 
    /* Mbi isn't hot-pluggable. No remove routine is provided */ 
 | 
    return mbi_pdev; 
 | 
} 
 | 
EXPORT_SYMBOL(iosf_mbi_available); 
 | 
  
 | 
void iosf_mbi_punit_acquire(void) 
 | 
{ 
 | 
    mutex_lock(&iosf_mbi_punit_mutex); 
 | 
} 
 | 
EXPORT_SYMBOL(iosf_mbi_punit_acquire); 
 | 
  
 | 
void iosf_mbi_punit_release(void) 
 | 
{ 
 | 
    mutex_unlock(&iosf_mbi_punit_mutex); 
 | 
} 
 | 
EXPORT_SYMBOL(iosf_mbi_punit_release); 
 | 
  
 | 
int iosf_mbi_register_pmic_bus_access_notifier(struct notifier_block *nb) 
 | 
{ 
 | 
    int ret; 
 | 
  
 | 
    /* Wait for the bus to go inactive before registering */ 
 | 
    mutex_lock(&iosf_mbi_punit_mutex); 
 | 
    ret = blocking_notifier_chain_register( 
 | 
                &iosf_mbi_pmic_bus_access_notifier, nb); 
 | 
    mutex_unlock(&iosf_mbi_punit_mutex); 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
EXPORT_SYMBOL(iosf_mbi_register_pmic_bus_access_notifier); 
 | 
  
 | 
int iosf_mbi_unregister_pmic_bus_access_notifier_unlocked( 
 | 
    struct notifier_block *nb) 
 | 
{ 
 | 
    iosf_mbi_assert_punit_acquired(); 
 | 
  
 | 
    return blocking_notifier_chain_unregister( 
 | 
                &iosf_mbi_pmic_bus_access_notifier, nb); 
 | 
} 
 | 
EXPORT_SYMBOL(iosf_mbi_unregister_pmic_bus_access_notifier_unlocked); 
 | 
  
 | 
int iosf_mbi_unregister_pmic_bus_access_notifier(struct notifier_block *nb) 
 | 
{ 
 | 
    int ret; 
 | 
  
 | 
    /* Wait for the bus to go inactive before unregistering */ 
 | 
    mutex_lock(&iosf_mbi_punit_mutex); 
 | 
    ret = iosf_mbi_unregister_pmic_bus_access_notifier_unlocked(nb); 
 | 
    mutex_unlock(&iosf_mbi_punit_mutex); 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
EXPORT_SYMBOL(iosf_mbi_unregister_pmic_bus_access_notifier); 
 | 
  
 | 
int iosf_mbi_call_pmic_bus_access_notifier_chain(unsigned long val, void *v) 
 | 
{ 
 | 
    return blocking_notifier_call_chain( 
 | 
                &iosf_mbi_pmic_bus_access_notifier, val, v); 
 | 
} 
 | 
EXPORT_SYMBOL(iosf_mbi_call_pmic_bus_access_notifier_chain); 
 | 
  
 | 
void iosf_mbi_assert_punit_acquired(void) 
 | 
{ 
 | 
    WARN_ON(!mutex_is_locked(&iosf_mbi_punit_mutex)); 
 | 
} 
 | 
EXPORT_SYMBOL(iosf_mbi_assert_punit_acquired); 
 | 
  
 | 
#ifdef CONFIG_IOSF_MBI_DEBUG 
 | 
static u32    dbg_mdr; 
 | 
static u32    dbg_mcr; 
 | 
static u32    dbg_mcrx; 
 | 
  
 | 
static int mcr_get(void *data, u64 *val) 
 | 
{ 
 | 
    *val = *(u32 *)data; 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int mcr_set(void *data, u64 val) 
 | 
{ 
 | 
    u8 command = ((u32)val & 0xFF000000) >> 24, 
 | 
       port       = ((u32)val & 0x00FF0000) >> 16, 
 | 
       offset  = ((u32)val & 0x0000FF00) >> 8; 
 | 
    int err; 
 | 
  
 | 
    *(u32 *)data = val; 
 | 
  
 | 
    if (!capable(CAP_SYS_RAWIO)) 
 | 
        return -EACCES; 
 | 
  
 | 
    if (command & 1u) 
 | 
        err = iosf_mbi_write(port, 
 | 
                   command, 
 | 
                   dbg_mcrx | offset, 
 | 
                   dbg_mdr); 
 | 
    else 
 | 
        err = iosf_mbi_read(port, 
 | 
                  command, 
 | 
                  dbg_mcrx | offset, 
 | 
                  &dbg_mdr); 
 | 
  
 | 
    return err; 
 | 
} 
 | 
DEFINE_SIMPLE_ATTRIBUTE(iosf_mcr_fops, mcr_get, mcr_set , "%llx\n"); 
 | 
  
 | 
static struct dentry *iosf_dbg; 
 | 
  
 | 
static void iosf_sideband_debug_init(void) 
 | 
{ 
 | 
    struct dentry *d; 
 | 
  
 | 
    iosf_dbg = debugfs_create_dir("iosf_sb", NULL); 
 | 
    if (IS_ERR_OR_NULL(iosf_dbg)) 
 | 
        return; 
 | 
  
 | 
    /* mdr */ 
 | 
    d = debugfs_create_x32("mdr", 0660, iosf_dbg, &dbg_mdr); 
 | 
    if (!d) 
 | 
        goto cleanup; 
 | 
  
 | 
    /* mcrx */ 
 | 
    d = debugfs_create_x32("mcrx", 0660, iosf_dbg, &dbg_mcrx); 
 | 
    if (!d) 
 | 
        goto cleanup; 
 | 
  
 | 
    /* mcr - initiates mailbox tranaction */ 
 | 
    d = debugfs_create_file("mcr", 0660, iosf_dbg, &dbg_mcr, &iosf_mcr_fops); 
 | 
    if (!d) 
 | 
        goto cleanup; 
 | 
  
 | 
    return; 
 | 
  
 | 
cleanup: 
 | 
    debugfs_remove_recursive(d); 
 | 
} 
 | 
  
 | 
static void iosf_debugfs_init(void) 
 | 
{ 
 | 
    iosf_sideband_debug_init(); 
 | 
} 
 | 
  
 | 
static void iosf_debugfs_remove(void) 
 | 
{ 
 | 
    debugfs_remove_recursive(iosf_dbg); 
 | 
} 
 | 
#else 
 | 
static inline void iosf_debugfs_init(void) { } 
 | 
static inline void iosf_debugfs_remove(void) { } 
 | 
#endif /* CONFIG_IOSF_MBI_DEBUG */ 
 | 
  
 | 
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 const struct pci_device_id iosf_mbi_pci_ids[] = { 
 | 
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_BAYTRAIL) }, 
 | 
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_BRASWELL) }, 
 | 
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_QUARK_X1000) }, 
 | 
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_TANGIER) }, 
 | 
    { 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) 
 | 
{ 
 | 
    iosf_debugfs_init(); 
 | 
  
 | 
    return pci_register_driver(&iosf_mbi_pci_driver); 
 | 
} 
 | 
  
 | 
static void __exit iosf_mbi_exit(void) 
 | 
{ 
 | 
    iosf_debugfs_remove(); 
 | 
  
 | 
    pci_unregister_driver(&iosf_mbi_pci_driver); 
 | 
    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"); 
 |