18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ipmi_si_pci.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Handling for IPMI devices on the PCI bus. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "ipmi_pci: " fmt 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/pci.h> 128c2ecf20Sopenharmony_ci#include "ipmi_si.h" 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_cistatic bool pci_registered; 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistatic bool si_trypci = true; 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cimodule_param_named(trypci, si_trypci, bool, 0); 198c2ecf20Sopenharmony_ciMODULE_PARM_DESC(trypci, "Setting this to zero will disable the" 208c2ecf20Sopenharmony_ci " default scan of the interfaces identified via pci"); 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define PCI_DEVICE_ID_HP_MMC 0x121A 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistatic void ipmi_pci_cleanup(struct si_sm_io *io) 258c2ecf20Sopenharmony_ci{ 268c2ecf20Sopenharmony_ci struct pci_dev *pdev = io->addr_source_data; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci pci_disable_device(pdev); 298c2ecf20Sopenharmony_ci} 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic int ipmi_pci_probe_regspacing(struct si_sm_io *io) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci if (io->si_type == SI_KCS) { 348c2ecf20Sopenharmony_ci unsigned char status; 358c2ecf20Sopenharmony_ci int regspacing; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci io->regsize = DEFAULT_REGSIZE; 388c2ecf20Sopenharmony_ci io->regshift = 0; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci /* detect 1, 4, 16byte spacing */ 418c2ecf20Sopenharmony_ci for (regspacing = DEFAULT_REGSPACING; regspacing <= 16;) { 428c2ecf20Sopenharmony_ci io->regspacing = regspacing; 438c2ecf20Sopenharmony_ci if (io->io_setup(io)) { 448c2ecf20Sopenharmony_ci dev_err(io->dev, "Could not setup I/O space\n"); 458c2ecf20Sopenharmony_ci return DEFAULT_REGSPACING; 468c2ecf20Sopenharmony_ci } 478c2ecf20Sopenharmony_ci /* write invalid cmd */ 488c2ecf20Sopenharmony_ci io->outputb(io, 1, 0x10); 498c2ecf20Sopenharmony_ci /* read status back */ 508c2ecf20Sopenharmony_ci status = io->inputb(io, 1); 518c2ecf20Sopenharmony_ci io->io_cleanup(io); 528c2ecf20Sopenharmony_ci if (status) 538c2ecf20Sopenharmony_ci return regspacing; 548c2ecf20Sopenharmony_ci regspacing *= 4; 558c2ecf20Sopenharmony_ci } 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci return DEFAULT_REGSPACING; 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic struct pci_device_id ipmi_pci_blacklist[] = { 618c2ecf20Sopenharmony_ci /* 628c2ecf20Sopenharmony_ci * This is a "Virtual IPMI device", whatever that is. It appears 638c2ecf20Sopenharmony_ci * as a KCS device by the class, but it is not one. 648c2ecf20Sopenharmony_ci */ 658c2ecf20Sopenharmony_ci { PCI_VDEVICE(REALTEK, 0x816c) }, 668c2ecf20Sopenharmony_ci { 0, } 678c2ecf20Sopenharmony_ci}; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int ipmi_pci_probe(struct pci_dev *pdev, 708c2ecf20Sopenharmony_ci const struct pci_device_id *ent) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci int rv; 738c2ecf20Sopenharmony_ci struct si_sm_io io; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (pci_match_id(ipmi_pci_blacklist, pdev)) 768c2ecf20Sopenharmony_ci return -ENODEV; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci memset(&io, 0, sizeof(io)); 798c2ecf20Sopenharmony_ci io.addr_source = SI_PCI; 808c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "probing via PCI"); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci switch (pdev->class) { 838c2ecf20Sopenharmony_ci case PCI_CLASS_SERIAL_IPMI_SMIC: 848c2ecf20Sopenharmony_ci io.si_type = SI_SMIC; 858c2ecf20Sopenharmony_ci break; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci case PCI_CLASS_SERIAL_IPMI_KCS: 888c2ecf20Sopenharmony_ci io.si_type = SI_KCS; 898c2ecf20Sopenharmony_ci break; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci case PCI_CLASS_SERIAL_IPMI_BT: 928c2ecf20Sopenharmony_ci io.si_type = SI_BT; 938c2ecf20Sopenharmony_ci break; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci default: 968c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "Unknown IPMI class: %x\n", pdev->class); 978c2ecf20Sopenharmony_ci return -ENOMEM; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci rv = pci_enable_device(pdev); 1018c2ecf20Sopenharmony_ci if (rv) { 1028c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "couldn't enable PCI device\n"); 1038c2ecf20Sopenharmony_ci return rv; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci io.addr_source_cleanup = ipmi_pci_cleanup; 1078c2ecf20Sopenharmony_ci io.addr_source_data = pdev; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci if (pci_resource_flags(pdev, 0) & IORESOURCE_IO) { 1108c2ecf20Sopenharmony_ci io.addr_space = IPMI_IO_ADDR_SPACE; 1118c2ecf20Sopenharmony_ci io.io_setup = ipmi_si_port_setup; 1128c2ecf20Sopenharmony_ci } else { 1138c2ecf20Sopenharmony_ci io.addr_space = IPMI_MEM_ADDR_SPACE; 1148c2ecf20Sopenharmony_ci io.io_setup = ipmi_si_mem_setup; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci io.addr_data = pci_resource_start(pdev, 0); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci io.dev = &pdev->dev; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci io.regspacing = ipmi_pci_probe_regspacing(&io); 1218c2ecf20Sopenharmony_ci io.regsize = DEFAULT_REGSIZE; 1228c2ecf20Sopenharmony_ci io.regshift = 0; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci io.irq = pdev->irq; 1258c2ecf20Sopenharmony_ci if (io.irq) 1268c2ecf20Sopenharmony_ci io.irq_setup = ipmi_std_irq_setup; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "%pR regsize %d spacing %d irq %d\n", 1298c2ecf20Sopenharmony_ci &pdev->resource[0], io.regsize, io.regspacing, io.irq); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci rv = ipmi_si_add_smi(&io); 1328c2ecf20Sopenharmony_ci if (rv) 1338c2ecf20Sopenharmony_ci pci_disable_device(pdev); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci return rv; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic void ipmi_pci_remove(struct pci_dev *pdev) 1398c2ecf20Sopenharmony_ci{ 1408c2ecf20Sopenharmony_ci ipmi_si_remove_by_dev(&pdev->dev); 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic const struct pci_device_id ipmi_pci_devices[] = { 1448c2ecf20Sopenharmony_ci { PCI_VDEVICE(HP, PCI_DEVICE_ID_HP_MMC) }, 1458c2ecf20Sopenharmony_ci { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_IPMI_SMIC, ~0) }, 1468c2ecf20Sopenharmony_ci { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_IPMI_KCS, ~0) }, 1478c2ecf20Sopenharmony_ci { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_IPMI_BT, ~0) }, 1488c2ecf20Sopenharmony_ci { 0, } 1498c2ecf20Sopenharmony_ci}; 1508c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, ipmi_pci_devices); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic struct pci_driver ipmi_pci_driver = { 1538c2ecf20Sopenharmony_ci .name = SI_DEVICE_NAME, 1548c2ecf20Sopenharmony_ci .id_table = ipmi_pci_devices, 1558c2ecf20Sopenharmony_ci .probe = ipmi_pci_probe, 1568c2ecf20Sopenharmony_ci .remove = ipmi_pci_remove, 1578c2ecf20Sopenharmony_ci}; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_civoid ipmi_si_pci_init(void) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci if (si_trypci) { 1628c2ecf20Sopenharmony_ci int rv = pci_register_driver(&ipmi_pci_driver); 1638c2ecf20Sopenharmony_ci if (rv) 1648c2ecf20Sopenharmony_ci pr_err("Unable to register PCI driver: %d\n", rv); 1658c2ecf20Sopenharmony_ci else 1668c2ecf20Sopenharmony_ci pci_registered = true; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_civoid ipmi_si_pci_shutdown(void) 1718c2ecf20Sopenharmony_ci{ 1728c2ecf20Sopenharmony_ci if (pci_registered) 1738c2ecf20Sopenharmony_ci pci_unregister_driver(&ipmi_pci_driver); 1748c2ecf20Sopenharmony_ci} 175