linux/arch/x86/platform/intel/iosf_mbi.c
<<
>>
Prefs
   1/*
   2 * IOSF-SB MailBox Interface Driver
   3 * Copyright (c) 2013, Intel Corporation.
   4 *
   5 * This program is free software; you can redistribute it and/or modify it
   6 * under the terms and conditions of the GNU General Public License,
   7 * version 2, as published by the Free Software Foundation.
   8 *
   9 * This program is distributed in the hope it will be useful, but WITHOUT
  10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  12 * more details.
  13 *
  14 *
  15 * The IOSF-SB is a fabric bus available on Atom based SOC's that uses a
  16 * mailbox interface (MBI) to communicate with mutiple devices. This
  17 * driver implements access to this interface for those platforms that can
  18 * enumerate the device using PCI.
  19 */
  20
  21#include <linux/module.h>
  22#include <linux/init.h>
  23#include <linux/spinlock.h>
  24#include <linux/pci.h>
  25#include <linux/debugfs.h>
  26#include <linux/capability.h>
  27
  28#include <asm/iosf_mbi.h>
  29
  30#define PCI_DEVICE_ID_BAYTRAIL          0x0F00
  31#define PCI_DEVICE_ID_BRASWELL          0x2280
  32#define PCI_DEVICE_ID_QUARK_X1000       0x0958
  33#define PCI_DEVICE_ID_TANGIER           0x1170
  34
  35static struct pci_dev *mbi_pdev;
  36static DEFINE_SPINLOCK(iosf_mbi_lock);
  37static DEFINE_MUTEX(iosf_mbi_punit_mutex);
  38static BLOCKING_NOTIFIER_HEAD(iosf_mbi_pmic_bus_access_notifier);
  39
  40static inline u32 iosf_mbi_form_mcr(u8 op, u8 port, u8 offset)
  41{
  42        return (op << 24) | (port << 16) | (offset << 8) | MBI_ENABLE;
  43}
  44
  45static int iosf_mbi_pci_read_mdr(u32 mcrx, u32 mcr, u32 *mdr)
  46{
  47        int result;
  48
  49        if (!mbi_pdev)
  50                return -ENODEV;
  51
  52        if (mcrx) {
  53                result = pci_write_config_dword(mbi_pdev, MBI_MCRX_OFFSET,
  54                                                mcrx);
  55                if (result < 0)
  56                        goto fail_read;
  57        }
  58
  59        result = pci_write_config_dword(mbi_pdev, MBI_MCR_OFFSET, mcr);
  60        if (result < 0)
  61                goto fail_read;
  62
  63        result = pci_read_config_dword(mbi_pdev, MBI_MDR_OFFSET, mdr);
  64        if (result < 0)
  65                goto fail_read;
  66
  67        return 0;
  68
  69fail_read:
  70        dev_err(&mbi_pdev->dev, "PCI config access failed with %d\n", result);
  71        return result;
  72}
  73
  74static int iosf_mbi_pci_write_mdr(u32 mcrx, u32 mcr, u32 mdr)
  75{
  76        int result;
  77
  78        if (!mbi_pdev)
  79                return -ENODEV;
  80
  81        result = pci_write_config_dword(mbi_pdev, MBI_MDR_OFFSET, mdr);
  82        if (result < 0)
  83                goto fail_write;
  84
  85        if (mcrx) {
  86                result = pci_write_config_dword(mbi_pdev, MBI_MCRX_OFFSET,
  87                                                mcrx);
  88                if (result < 0)
  89                        goto fail_write;
  90        }
  91
  92        result = pci_write_config_dword(mbi_pdev, MBI_MCR_OFFSET, mcr);
  93        if (result < 0)
  94                goto fail_write;
  95
  96        return 0;
  97
  98fail_write:
  99        dev_err(&mbi_pdev->dev, "PCI config access failed with %d\n", result);
 100        return result;
 101}
 102
 103int iosf_mbi_read(u8 port, u8 opcode, u32 offset, u32 *mdr)
 104{
 105        u32 mcr, mcrx;
 106        unsigned long flags;
 107        int ret;
 108
 109        /* Access to the GFX unit is handled by GPU code */
 110        if (port == BT_MBI_UNIT_GFX) {
 111                WARN_ON(1);
 112                return -EPERM;
 113        }
 114
 115        mcr = iosf_mbi_form_mcr(opcode, port, offset & MBI_MASK_LO);
 116        mcrx = offset & MBI_MASK_HI;
 117
 118        spin_lock_irqsave(&iosf_mbi_lock, flags);
 119        ret = iosf_mbi_pci_read_mdr(mcrx, mcr, mdr);
 120        spin_unlock_irqrestore(&iosf_mbi_lock, flags);
 121
 122        return ret;
 123}
 124EXPORT_SYMBOL(iosf_mbi_read);
 125
 126int iosf_mbi_write(u8 port, u8 opcode, u32 offset, u32 mdr)
 127{
 128        u32 mcr, mcrx;
 129        unsigned long flags;
 130        int ret;
 131
 132        /* Access to the GFX unit is handled by GPU code */
 133        if (port == BT_MBI_UNIT_GFX) {
 134                WARN_ON(1);
 135                return -EPERM;
 136        }
 137
 138        mcr = iosf_mbi_form_mcr(opcode, port, offset & MBI_MASK_LO);
 139        mcrx = offset & MBI_MASK_HI;
 140
 141        spin_lock_irqsave(&iosf_mbi_lock, flags);
 142        ret = iosf_mbi_pci_write_mdr(mcrx, mcr, mdr);
 143        spin_unlock_irqrestore(&iosf_mbi_lock, flags);
 144
 145        return ret;
 146}
 147EXPORT_SYMBOL(iosf_mbi_write);
 148
 149int iosf_mbi_modify(u8 port, u8 opcode, u32 offset, u32 mdr, u32 mask)
 150{
 151        u32 mcr, mcrx;
 152        u32 value;
 153        unsigned long flags;
 154        int ret;
 155
 156        /* Access to the GFX unit is handled by GPU code */
 157        if (port == BT_MBI_UNIT_GFX) {
 158                WARN_ON(1);
 159                return -EPERM;
 160        }
 161
 162        mcr = iosf_mbi_form_mcr(opcode, port, offset & MBI_MASK_LO);
 163        mcrx = offset & MBI_MASK_HI;
 164
 165        spin_lock_irqsave(&iosf_mbi_lock, flags);
 166
 167        /* Read current mdr value */
 168        ret = iosf_mbi_pci_read_mdr(mcrx, mcr & MBI_RD_MASK, &value);
 169        if (ret < 0) {
 170                spin_unlock_irqrestore(&iosf_mbi_lock, flags);
 171                return ret;
 172        }
 173
 174        /* Apply mask */
 175        value &= ~mask;
 176        mdr &= mask;
 177        value |= mdr;
 178
 179        /* Write back */
 180        ret = iosf_mbi_pci_write_mdr(mcrx, mcr | MBI_WR_MASK, value);
 181
 182        spin_unlock_irqrestore(&iosf_mbi_lock, flags);
 183
 184        return ret;
 185}
 186EXPORT_SYMBOL(iosf_mbi_modify);
 187
 188bool iosf_mbi_available(void)
 189{
 190        /* Mbi isn't hot-pluggable. No remove routine is provided */
 191        return mbi_pdev;
 192}
 193EXPORT_SYMBOL(iosf_mbi_available);
 194
 195void iosf_mbi_punit_acquire(void)
 196{
 197        mutex_lock(&iosf_mbi_punit_mutex);
 198}
 199EXPORT_SYMBOL(iosf_mbi_punit_acquire);
 200
 201void iosf_mbi_punit_release(void)
 202{
 203        mutex_unlock(&iosf_mbi_punit_mutex);
 204}
 205EXPORT_SYMBOL(iosf_mbi_punit_release);
 206
 207int iosf_mbi_register_pmic_bus_access_notifier(struct notifier_block *nb)
 208{
 209        int ret;
 210
 211        /* Wait for the bus to go inactive before registering */
 212        mutex_lock(&iosf_mbi_punit_mutex);
 213        ret = blocking_notifier_chain_register(
 214                                &iosf_mbi_pmic_bus_access_notifier, nb);
 215        mutex_unlock(&iosf_mbi_punit_mutex);
 216
 217        return ret;
 218}
 219EXPORT_SYMBOL(iosf_mbi_register_pmic_bus_access_notifier);
 220
 221int iosf_mbi_unregister_pmic_bus_access_notifier_unlocked(
 222        struct notifier_block *nb)
 223{
 224        iosf_mbi_assert_punit_acquired();
 225
 226        return blocking_notifier_chain_unregister(
 227                                &iosf_mbi_pmic_bus_access_notifier, nb);
 228}
 229EXPORT_SYMBOL(iosf_mbi_unregister_pmic_bus_access_notifier_unlocked);
 230
 231int iosf_mbi_unregister_pmic_bus_access_notifier(struct notifier_block *nb)
 232{
 233        int ret;
 234
 235        /* Wait for the bus to go inactive before unregistering */
 236        mutex_lock(&iosf_mbi_punit_mutex);
 237        ret = iosf_mbi_unregister_pmic_bus_access_notifier_unlocked(nb);
 238        mutex_unlock(&iosf_mbi_punit_mutex);
 239
 240        return ret;
 241}
 242EXPORT_SYMBOL(iosf_mbi_unregister_pmic_bus_access_notifier);
 243
 244int iosf_mbi_call_pmic_bus_access_notifier_chain(unsigned long val, void *v)
 245{
 246        return blocking_notifier_call_chain(
 247                                &iosf_mbi_pmic_bus_access_notifier, val, v);
 248}
 249EXPORT_SYMBOL(iosf_mbi_call_pmic_bus_access_notifier_chain);
 250
 251void iosf_mbi_assert_punit_acquired(void)
 252{
 253        WARN_ON(!mutex_is_locked(&iosf_mbi_punit_mutex));
 254}
 255EXPORT_SYMBOL(iosf_mbi_assert_punit_acquired);
 256
 257#ifdef CONFIG_IOSF_MBI_DEBUG
 258static u32      dbg_mdr;
 259static u32      dbg_mcr;
 260static u32      dbg_mcrx;
 261
 262static int mcr_get(void *data, u64 *val)
 263{
 264        *val = *(u32 *)data;
 265        return 0;
 266}
 267
 268static int mcr_set(void *data, u64 val)
 269{
 270        u8 command = ((u32)val & 0xFF000000) >> 24,
 271           port    = ((u32)val & 0x00FF0000) >> 16,
 272           offset  = ((u32)val & 0x0000FF00) >> 8;
 273        int err;
 274
 275        *(u32 *)data = val;
 276
 277        if (!capable(CAP_SYS_RAWIO))
 278                return -EACCES;
 279
 280        if (command & 1u)
 281                err = iosf_mbi_write(port,
 282                               command,
 283                               dbg_mcrx | offset,
 284                               dbg_mdr);
 285        else
 286                err = iosf_mbi_read(port,
 287                              command,
 288                              dbg_mcrx | offset,
 289                              &dbg_mdr);
 290
 291        return err;
 292}
 293DEFINE_SIMPLE_ATTRIBUTE(iosf_mcr_fops, mcr_get, mcr_set , "%llx\n");
 294
 295static struct dentry *iosf_dbg;
 296
 297static void iosf_sideband_debug_init(void)
 298{
 299        struct dentry *d;
 300
 301        iosf_dbg = debugfs_create_dir("iosf_sb", NULL);
 302        if (IS_ERR_OR_NULL(iosf_dbg))
 303                return;
 304
 305        /* mdr */
 306        d = debugfs_create_x32("mdr", 0660, iosf_dbg, &dbg_mdr);
 307        if (!d)
 308                goto cleanup;
 309
 310        /* mcrx */
 311        d = debugfs_create_x32("mcrx", 0660, iosf_dbg, &dbg_mcrx);
 312        if (!d)
 313                goto cleanup;
 314
 315        /* mcr - initiates mailbox tranaction */
 316        d = debugfs_create_file("mcr", 0660, iosf_dbg, &dbg_mcr, &iosf_mcr_fops);
 317        if (!d)
 318                goto cleanup;
 319
 320        return;
 321
 322cleanup:
 323        debugfs_remove_recursive(d);
 324}
 325
 326static void iosf_debugfs_init(void)
 327{
 328        iosf_sideband_debug_init();
 329}
 330
 331static void iosf_debugfs_remove(void)
 332{
 333        debugfs_remove_recursive(iosf_dbg);
 334}
 335#else
 336static inline void iosf_debugfs_init(void) { }
 337static inline void iosf_debugfs_remove(void) { }
 338#endif /* CONFIG_IOSF_MBI_DEBUG */
 339
 340static int iosf_mbi_probe(struct pci_dev *pdev,
 341                          const struct pci_device_id *unused)
 342{
 343        int ret;
 344
 345        ret = pci_enable_device(pdev);
 346        if (ret < 0) {
 347                dev_err(&pdev->dev, "error: could not enable device\n");
 348                return ret;
 349        }
 350
 351        mbi_pdev = pci_dev_get(pdev);
 352        return 0;
 353}
 354
 355static const struct pci_device_id iosf_mbi_pci_ids[] = {
 356        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_BAYTRAIL) },
 357        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_BRASWELL) },
 358        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_QUARK_X1000) },
 359        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_TANGIER) },
 360        { 0, },
 361};
 362MODULE_DEVICE_TABLE(pci, iosf_mbi_pci_ids);
 363
 364static struct pci_driver iosf_mbi_pci_driver = {
 365        .name           = "iosf_mbi_pci",
 366        .probe          = iosf_mbi_probe,
 367        .id_table       = iosf_mbi_pci_ids,
 368};
 369
 370static int __init iosf_mbi_init(void)
 371{
 372        iosf_debugfs_init();
 373
 374        return pci_register_driver(&iosf_mbi_pci_driver);
 375}
 376
 377static void __exit iosf_mbi_exit(void)
 378{
 379        iosf_debugfs_remove();
 380
 381        pci_unregister_driver(&iosf_mbi_pci_driver);
 382        pci_dev_put(mbi_pdev);
 383        mbi_pdev = NULL;
 384}
 385
 386module_init(iosf_mbi_init);
 387module_exit(iosf_mbi_exit);
 388
 389MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
 390MODULE_DESCRIPTION("IOSF Mailbox Interface accessor");
 391MODULE_LICENSE("GPL v2");
 392