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 34static DEFINE_SPINLOCK(iosf_mbi_lock); 35 36static inline u32 iosf_mbi_form_mcr(u8 op, u8 port, u8 offset) 37{ 38 return (op << 24) | (port << 16) | (offset << 8) | MBI_ENABLE; 39} 40 41static struct pci_dev *mbi_pdev; /* one mbi device */ 42 43static int iosf_mbi_pci_read_mdr(u32 mcrx, u32 mcr, u32 *mdr) 44{ 45 int result; 46 47 if (!mbi_pdev) 48 return -ENODEV; 49 50 if (mcrx) { 51 result = pci_write_config_dword(mbi_pdev, MBI_MCRX_OFFSET, 52 mcrx); 53 if (result < 0) 54 goto fail_read; 55 } 56 57 result = pci_write_config_dword(mbi_pdev, MBI_MCR_OFFSET, mcr); 58 if (result < 0) 59 goto fail_read; 60 61 result = pci_read_config_dword(mbi_pdev, MBI_MDR_OFFSET, mdr); 62 if (result < 0) 63 goto fail_read; 64 65 return 0; 66 67fail_read: 68 dev_err(&mbi_pdev->dev, "PCI config access failed with %d\n", result); 69 return result; 70} 71 72static int iosf_mbi_pci_write_mdr(u32 mcrx, u32 mcr, u32 mdr) 73{ 74 int result; 75 76 if (!mbi_pdev) 77 return -ENODEV; 78 79 result = pci_write_config_dword(mbi_pdev, MBI_MDR_OFFSET, mdr); 80 if (result < 0) 81 goto fail_write; 82 83 if (mcrx) { 84 result = pci_write_config_dword(mbi_pdev, MBI_MCRX_OFFSET, 85 mcrx); 86 if (result < 0) 87 goto fail_write; 88 } 89 90 result = pci_write_config_dword(mbi_pdev, MBI_MCR_OFFSET, mcr); 91 if (result < 0) 92 goto fail_write; 93 94 return 0; 95 96fail_write: 97 dev_err(&mbi_pdev->dev, "PCI config access failed with %d\n", result); 98 return result; 99} 100 101int iosf_mbi_read(u8 port, u8 opcode, u32 offset, u32 *mdr) 102{ 103 u32 mcr, mcrx; 104 unsigned long flags; 105 int ret; 106 107 /*Access to the GFX unit is handled by GPU code */ 108 if (port == BT_MBI_UNIT_GFX) { 109 WARN_ON(1); 110 return -EPERM; 111 } 112 113 mcr = iosf_mbi_form_mcr(opcode, port, offset & MBI_MASK_LO); 114 mcrx = offset & MBI_MASK_HI; 115 116 spin_lock_irqsave(&iosf_mbi_lock, flags); 117 ret = iosf_mbi_pci_read_mdr(mcrx, mcr, mdr); 118 spin_unlock_irqrestore(&iosf_mbi_lock, flags); 119 120 return ret; 121} 122EXPORT_SYMBOL(iosf_mbi_read); 123 124int iosf_mbi_write(u8 port, u8 opcode, u32 offset, u32 mdr) 125{ 126 u32 mcr, mcrx; 127 unsigned long flags; 128 int ret; 129 130 /*Access to the GFX unit is handled by GPU code */ 131 if (port == BT_MBI_UNIT_GFX) { 132 WARN_ON(1); 133 return -EPERM; 134 } 135 136 mcr = iosf_mbi_form_mcr(opcode, port, offset & MBI_MASK_LO); 137 mcrx = offset & MBI_MASK_HI; 138 139 spin_lock_irqsave(&iosf_mbi_lock, flags); 140 ret = iosf_mbi_pci_write_mdr(mcrx, mcr, mdr); 141 spin_unlock_irqrestore(&iosf_mbi_lock, flags); 142 143 return ret; 144} 145EXPORT_SYMBOL(iosf_mbi_write); 146 147int iosf_mbi_modify(u8 port, u8 opcode, u32 offset, u32 mdr, u32 mask) 148{ 149 u32 mcr, mcrx; 150 u32 value; 151 unsigned long flags; 152 int ret; 153 154 /*Access to the GFX unit is handled by GPU code */ 155 if (port == BT_MBI_UNIT_GFX) { 156 WARN_ON(1); 157 return -EPERM; 158 } 159 160 mcr = iosf_mbi_form_mcr(opcode, port, offset & MBI_MASK_LO); 161 mcrx = offset & MBI_MASK_HI; 162 163 spin_lock_irqsave(&iosf_mbi_lock, flags); 164 165 /* Read current mdr value */ 166 ret = iosf_mbi_pci_read_mdr(mcrx, mcr & MBI_RD_MASK, &value); 167 if (ret < 0) { 168 spin_unlock_irqrestore(&iosf_mbi_lock, flags); 169 return ret; 170 } 171 172 /* Apply mask */ 173 value &= ~mask; 174 mdr &= mask; 175 value |= mdr; 176 177 /* Write back */ 178 ret = iosf_mbi_pci_write_mdr(mcrx, mcr | MBI_WR_MASK, value); 179 180 spin_unlock_irqrestore(&iosf_mbi_lock, flags); 181 182 return ret; 183} 184EXPORT_SYMBOL(iosf_mbi_modify); 185 186bool iosf_mbi_available(void) 187{ 188 /* Mbi isn't hot-pluggable. No remove routine is provided */ 189 return mbi_pdev; 190} 191EXPORT_SYMBOL(iosf_mbi_available); 192 193#ifdef CONFIG_IOSF_MBI_DEBUG 194static u32 dbg_mdr; 195static u32 dbg_mcr; 196static u32 dbg_mcrx; 197 198static int mcr_get(void *data, u64 *val) 199{ 200 *val = *(u32 *)data; 201 return 0; 202} 203 204static int mcr_set(void *data, u64 val) 205{ 206 u8 command = ((u32)val & 0xFF000000) >> 24, 207 port = ((u32)val & 0x00FF0000) >> 16, 208 offset = ((u32)val & 0x0000FF00) >> 8; 209 int err; 210 211 *(u32 *)data = val; 212 213 if (!capable(CAP_SYS_RAWIO)) 214 return -EACCES; 215 216 if (command & 1u) 217 err = iosf_mbi_write(port, 218 command, 219 dbg_mcrx | offset, 220 dbg_mdr); 221 else 222 err = iosf_mbi_read(port, 223 command, 224 dbg_mcrx | offset, 225 &dbg_mdr); 226 227 return err; 228} 229DEFINE_SIMPLE_ATTRIBUTE(iosf_mcr_fops, mcr_get, mcr_set , "%llx\n"); 230 231static struct dentry *iosf_dbg; 232 233static void iosf_sideband_debug_init(void) 234{ 235 struct dentry *d; 236 237 iosf_dbg = debugfs_create_dir("iosf_sb", NULL); 238 if (IS_ERR_OR_NULL(iosf_dbg)) 239 return; 240 241 /* mdr */ 242 d = debugfs_create_x32("mdr", 0660, iosf_dbg, &dbg_mdr); 243 if (IS_ERR_OR_NULL(d)) 244 goto cleanup; 245 246 /* mcrx */ 247 debugfs_create_x32("mcrx", 0660, iosf_dbg, &dbg_mcrx); 248 if (IS_ERR_OR_NULL(d)) 249 goto cleanup; 250 251 /* mcr - initiates mailbox tranaction */ 252 debugfs_create_file("mcr", 0660, iosf_dbg, &dbg_mcr, &iosf_mcr_fops); 253 if (IS_ERR_OR_NULL(d)) 254 goto cleanup; 255 256 return; 257 258cleanup: 259 debugfs_remove_recursive(d); 260} 261 262static void iosf_debugfs_init(void) 263{ 264 iosf_sideband_debug_init(); 265} 266 267static void iosf_debugfs_remove(void) 268{ 269 debugfs_remove_recursive(iosf_dbg); 270} 271#else 272static inline void iosf_debugfs_init(void) { } 273static inline void iosf_debugfs_remove(void) { } 274#endif /* CONFIG_IOSF_MBI_DEBUG */ 275 276static int iosf_mbi_probe(struct pci_dev *pdev, 277 const struct pci_device_id *unused) 278{ 279 int ret; 280 281 ret = pci_enable_device(pdev); 282 if (ret < 0) { 283 dev_err(&pdev->dev, "error: could not enable device\n"); 284 return ret; 285 } 286 287 mbi_pdev = pci_dev_get(pdev); 288 return 0; 289} 290 291static const struct pci_device_id iosf_mbi_pci_ids[] = { 292 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_BAYTRAIL) }, 293 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_BRASWELL) }, 294 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_QUARK_X1000) }, 295 { 0, }, 296}; 297MODULE_DEVICE_TABLE(pci, iosf_mbi_pci_ids); 298 299static struct pci_driver iosf_mbi_pci_driver = { 300 .name = "iosf_mbi_pci", 301 .probe = iosf_mbi_probe, 302 .id_table = iosf_mbi_pci_ids, 303}; 304 305static int __init iosf_mbi_init(void) 306{ 307 iosf_debugfs_init(); 308 309 return pci_register_driver(&iosf_mbi_pci_driver); 310} 311 312static void __exit iosf_mbi_exit(void) 313{ 314 iosf_debugfs_remove(); 315 316 pci_unregister_driver(&iosf_mbi_pci_driver); 317 if (mbi_pdev) { 318 pci_dev_put(mbi_pdev); 319 mbi_pdev = NULL; 320 } 321} 322 323module_init(iosf_mbi_init); 324module_exit(iosf_mbi_exit); 325 326MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>"); 327MODULE_DESCRIPTION("IOSF Mailbox Interface accessor"); 328MODULE_LICENSE("GPL v2"); 329