162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * cb710/core.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright by Michał Mirosław, 2008-2009 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include <linux/kernel.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/pci.h> 1062306a36Sopenharmony_ci#include <linux/spinlock.h> 1162306a36Sopenharmony_ci#include <linux/idr.h> 1262306a36Sopenharmony_ci#include <linux/cb710.h> 1362306a36Sopenharmony_ci#include <linux/gfp.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistatic DEFINE_IDA(cb710_ida); 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_civoid cb710_pci_update_config_reg(struct pci_dev *pdev, 1862306a36Sopenharmony_ci int reg, uint32_t mask, uint32_t xor) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci u32 rval; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci pci_read_config_dword(pdev, reg, &rval); 2362306a36Sopenharmony_ci rval = (rval & mask) ^ xor; 2462306a36Sopenharmony_ci pci_write_config_dword(pdev, reg, rval); 2562306a36Sopenharmony_ci} 2662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cb710_pci_update_config_reg); 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/* Some magic writes based on Windows driver init code */ 2962306a36Sopenharmony_cistatic int cb710_pci_configure(struct pci_dev *pdev) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0); 3262306a36Sopenharmony_ci struct pci_dev *pdev0; 3362306a36Sopenharmony_ci u32 val; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci cb710_pci_update_config_reg(pdev, 0x48, 3662306a36Sopenharmony_ci ~0x000000FF, 0x0000003F); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci pci_read_config_dword(pdev, 0x48, &val); 3962306a36Sopenharmony_ci if (val & 0x80000000) 4062306a36Sopenharmony_ci return 0; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci pdev0 = pci_get_slot(pdev->bus, devfn); 4362306a36Sopenharmony_ci if (!pdev0) 4462306a36Sopenharmony_ci return -ENODEV; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci if (pdev0->vendor == PCI_VENDOR_ID_ENE 4762306a36Sopenharmony_ci && pdev0->device == PCI_DEVICE_ID_ENE_720) { 4862306a36Sopenharmony_ci cb710_pci_update_config_reg(pdev0, 0x8C, 4962306a36Sopenharmony_ci ~0x00F00000, 0x00100000); 5062306a36Sopenharmony_ci cb710_pci_update_config_reg(pdev0, 0xB0, 5162306a36Sopenharmony_ci ~0x08000000, 0x08000000); 5262306a36Sopenharmony_ci } 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci cb710_pci_update_config_reg(pdev0, 0x8C, 5562306a36Sopenharmony_ci ~0x00000F00, 0x00000200); 5662306a36Sopenharmony_ci cb710_pci_update_config_reg(pdev0, 0x90, 5762306a36Sopenharmony_ci ~0x00060000, 0x00040000); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci pci_dev_put(pdev0); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci return 0; 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic irqreturn_t cb710_irq_handler(int irq, void *data) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci struct cb710_chip *chip = data; 6762306a36Sopenharmony_ci struct cb710_slot *slot = &chip->slot[0]; 6862306a36Sopenharmony_ci irqreturn_t handled = IRQ_NONE; 6962306a36Sopenharmony_ci unsigned nr; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci spin_lock(&chip->irq_lock); /* incl. smp_rmb() */ 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci for (nr = chip->slots; nr; ++slot, --nr) { 7462306a36Sopenharmony_ci cb710_irq_handler_t handler_func = slot->irq_handler; 7562306a36Sopenharmony_ci if (handler_func && handler_func(slot)) 7662306a36Sopenharmony_ci handled = IRQ_HANDLED; 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci spin_unlock(&chip->irq_lock); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci return handled; 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic void cb710_release_slot(struct device *dev) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS 8762306a36Sopenharmony_ci struct cb710_slot *slot = cb710_pdev_to_slot(to_platform_device(dev)); 8862306a36Sopenharmony_ci struct cb710_chip *chip = cb710_slot_to_chip(slot); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci /* slot struct can be freed now */ 9162306a36Sopenharmony_ci atomic_dec(&chip->slot_refs_count); 9262306a36Sopenharmony_ci#endif 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic int cb710_register_slot(struct cb710_chip *chip, 9662306a36Sopenharmony_ci unsigned slot_mask, unsigned io_offset, const char *name) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci int nr = chip->slots; 9962306a36Sopenharmony_ci struct cb710_slot *slot = &chip->slot[nr]; 10062306a36Sopenharmony_ci int err; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci dev_dbg(cb710_chip_dev(chip), 10362306a36Sopenharmony_ci "register: %s.%d; slot %d; mask %d; IO offset: 0x%02X\n", 10462306a36Sopenharmony_ci name, chip->platform_id, nr, slot_mask, io_offset); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* slot->irq_handler == NULL here; this needs to be 10762306a36Sopenharmony_ci * seen before platform_device_register() */ 10862306a36Sopenharmony_ci ++chip->slots; 10962306a36Sopenharmony_ci smp_wmb(); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci slot->iobase = chip->iobase + io_offset; 11262306a36Sopenharmony_ci slot->pdev.name = name; 11362306a36Sopenharmony_ci slot->pdev.id = chip->platform_id; 11462306a36Sopenharmony_ci slot->pdev.dev.parent = &chip->pdev->dev; 11562306a36Sopenharmony_ci slot->pdev.dev.release = cb710_release_slot; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci err = platform_device_register(&slot->pdev); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS 12062306a36Sopenharmony_ci atomic_inc(&chip->slot_refs_count); 12162306a36Sopenharmony_ci#endif 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (err) { 12462306a36Sopenharmony_ci /* device_initialize() called from platform_device_register() 12562306a36Sopenharmony_ci * wants this on error path */ 12662306a36Sopenharmony_ci platform_device_put(&slot->pdev); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* slot->irq_handler == NULL here anyway, so no lock needed */ 12962306a36Sopenharmony_ci --chip->slots; 13062306a36Sopenharmony_ci return err; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci chip->slot_mask |= slot_mask; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci return 0; 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic void cb710_unregister_slot(struct cb710_chip *chip, 13962306a36Sopenharmony_ci unsigned slot_mask) 14062306a36Sopenharmony_ci{ 14162306a36Sopenharmony_ci int nr = chip->slots - 1; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci if (!(chip->slot_mask & slot_mask)) 14462306a36Sopenharmony_ci return; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci platform_device_unregister(&chip->slot[nr].pdev); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci /* complementary to spin_unlock() in cb710_set_irq_handler() */ 14962306a36Sopenharmony_ci smp_rmb(); 15062306a36Sopenharmony_ci BUG_ON(chip->slot[nr].irq_handler != NULL); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci /* slot->irq_handler == NULL here, so no lock needed */ 15362306a36Sopenharmony_ci --chip->slots; 15462306a36Sopenharmony_ci chip->slot_mask &= ~slot_mask; 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_civoid cb710_set_irq_handler(struct cb710_slot *slot, 15862306a36Sopenharmony_ci cb710_irq_handler_t handler) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct cb710_chip *chip = cb710_slot_to_chip(slot); 16162306a36Sopenharmony_ci unsigned long flags; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci spin_lock_irqsave(&chip->irq_lock, flags); 16462306a36Sopenharmony_ci slot->irq_handler = handler; 16562306a36Sopenharmony_ci spin_unlock_irqrestore(&chip->irq_lock, flags); 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cb710_set_irq_handler); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic int __maybe_unused cb710_suspend(struct device *dev_d) 17062306a36Sopenharmony_ci{ 17162306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(dev_d); 17262306a36Sopenharmony_ci struct cb710_chip *chip = pci_get_drvdata(pdev); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci devm_free_irq(&pdev->dev, pdev->irq, chip); 17562306a36Sopenharmony_ci return 0; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic int __maybe_unused cb710_resume(struct device *dev_d) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(dev_d); 18162306a36Sopenharmony_ci struct cb710_chip *chip = pci_get_drvdata(pdev); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci return devm_request_irq(&pdev->dev, pdev->irq, 18462306a36Sopenharmony_ci cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip); 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic int cb710_probe(struct pci_dev *pdev, 18862306a36Sopenharmony_ci const struct pci_device_id *ent) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci struct cb710_chip *chip; 19162306a36Sopenharmony_ci u32 val; 19262306a36Sopenharmony_ci int err; 19362306a36Sopenharmony_ci int n = 0; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci err = cb710_pci_configure(pdev); 19662306a36Sopenharmony_ci if (err) 19762306a36Sopenharmony_ci return err; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* this is actually magic... */ 20062306a36Sopenharmony_ci pci_read_config_dword(pdev, 0x48, &val); 20162306a36Sopenharmony_ci if (!(val & 0x80000000)) { 20262306a36Sopenharmony_ci pci_write_config_dword(pdev, 0x48, val|0x71000000); 20362306a36Sopenharmony_ci pci_read_config_dword(pdev, 0x48, &val); 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci dev_dbg(&pdev->dev, "PCI config[0x48] = 0x%08X\n", val); 20762306a36Sopenharmony_ci if (!(val & 0x70000000)) 20862306a36Sopenharmony_ci return -ENODEV; 20962306a36Sopenharmony_ci val = (val >> 28) & 7; 21062306a36Sopenharmony_ci if (val & CB710_SLOT_MMC) 21162306a36Sopenharmony_ci ++n; 21262306a36Sopenharmony_ci if (val & CB710_SLOT_MS) 21362306a36Sopenharmony_ci ++n; 21462306a36Sopenharmony_ci if (val & CB710_SLOT_SM) 21562306a36Sopenharmony_ci ++n; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci chip = devm_kzalloc(&pdev->dev, struct_size(chip, slot, n), 21862306a36Sopenharmony_ci GFP_KERNEL); 21962306a36Sopenharmony_ci if (!chip) 22062306a36Sopenharmony_ci return -ENOMEM; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci err = pcim_enable_device(pdev); 22362306a36Sopenharmony_ci if (err) 22462306a36Sopenharmony_ci return err; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci err = pcim_iomap_regions(pdev, 0x0001, KBUILD_MODNAME); 22762306a36Sopenharmony_ci if (err) 22862306a36Sopenharmony_ci return err; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci spin_lock_init(&chip->irq_lock); 23162306a36Sopenharmony_ci chip->pdev = pdev; 23262306a36Sopenharmony_ci chip->iobase = pcim_iomap_table(pdev)[0]; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci pci_set_drvdata(pdev, chip); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci err = devm_request_irq(&pdev->dev, pdev->irq, 23762306a36Sopenharmony_ci cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip); 23862306a36Sopenharmony_ci if (err) 23962306a36Sopenharmony_ci return err; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci err = ida_alloc(&cb710_ida, GFP_KERNEL); 24262306a36Sopenharmony_ci if (err < 0) 24362306a36Sopenharmony_ci return err; 24462306a36Sopenharmony_ci chip->platform_id = err; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci dev_info(&pdev->dev, "id %d, IO 0x%p, IRQ %d\n", 24762306a36Sopenharmony_ci chip->platform_id, chip->iobase, pdev->irq); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci if (val & CB710_SLOT_MMC) { /* MMC/SD slot */ 25062306a36Sopenharmony_ci err = cb710_register_slot(chip, 25162306a36Sopenharmony_ci CB710_SLOT_MMC, 0x00, "cb710-mmc"); 25262306a36Sopenharmony_ci if (err) 25362306a36Sopenharmony_ci return err; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci if (val & CB710_SLOT_MS) { /* MemoryStick slot */ 25762306a36Sopenharmony_ci err = cb710_register_slot(chip, 25862306a36Sopenharmony_ci CB710_SLOT_MS, 0x40, "cb710-ms"); 25962306a36Sopenharmony_ci if (err) 26062306a36Sopenharmony_ci goto unreg_mmc; 26162306a36Sopenharmony_ci } 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci if (val & CB710_SLOT_SM) { /* SmartMedia slot */ 26462306a36Sopenharmony_ci err = cb710_register_slot(chip, 26562306a36Sopenharmony_ci CB710_SLOT_SM, 0x60, "cb710-sm"); 26662306a36Sopenharmony_ci if (err) 26762306a36Sopenharmony_ci goto unreg_ms; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci return 0; 27162306a36Sopenharmony_ciunreg_ms: 27262306a36Sopenharmony_ci cb710_unregister_slot(chip, CB710_SLOT_MS); 27362306a36Sopenharmony_ciunreg_mmc: 27462306a36Sopenharmony_ci cb710_unregister_slot(chip, CB710_SLOT_MMC); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS 27762306a36Sopenharmony_ci BUG_ON(atomic_read(&chip->slot_refs_count) != 0); 27862306a36Sopenharmony_ci#endif 27962306a36Sopenharmony_ci return err; 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic void cb710_remove_one(struct pci_dev *pdev) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci struct cb710_chip *chip = pci_get_drvdata(pdev); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci cb710_unregister_slot(chip, CB710_SLOT_SM); 28762306a36Sopenharmony_ci cb710_unregister_slot(chip, CB710_SLOT_MS); 28862306a36Sopenharmony_ci cb710_unregister_slot(chip, CB710_SLOT_MMC); 28962306a36Sopenharmony_ci#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS 29062306a36Sopenharmony_ci BUG_ON(atomic_read(&chip->slot_refs_count) != 0); 29162306a36Sopenharmony_ci#endif 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci ida_free(&cb710_ida, chip->platform_id); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic const struct pci_device_id cb710_pci_tbl[] = { 29762306a36Sopenharmony_ci { PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_CB710_FLASH, 29862306a36Sopenharmony_ci PCI_ANY_ID, PCI_ANY_ID, }, 29962306a36Sopenharmony_ci { 0, } 30062306a36Sopenharmony_ci}; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(cb710_pm_ops, cb710_suspend, cb710_resume); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic struct pci_driver cb710_driver = { 30562306a36Sopenharmony_ci .name = KBUILD_MODNAME, 30662306a36Sopenharmony_ci .id_table = cb710_pci_tbl, 30762306a36Sopenharmony_ci .probe = cb710_probe, 30862306a36Sopenharmony_ci .remove = cb710_remove_one, 30962306a36Sopenharmony_ci .driver.pm = &cb710_pm_ops, 31062306a36Sopenharmony_ci}; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_cistatic int __init cb710_init_module(void) 31362306a36Sopenharmony_ci{ 31462306a36Sopenharmony_ci return pci_register_driver(&cb710_driver); 31562306a36Sopenharmony_ci} 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_cistatic void __exit cb710_cleanup_module(void) 31862306a36Sopenharmony_ci{ 31962306a36Sopenharmony_ci pci_unregister_driver(&cb710_driver); 32062306a36Sopenharmony_ci ida_destroy(&cb710_ida); 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_cimodule_init(cb710_init_module); 32462306a36Sopenharmony_cimodule_exit(cb710_cleanup_module); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ciMODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>"); 32762306a36Sopenharmony_ciMODULE_DESCRIPTION("ENE CB710 memory card reader driver"); 32862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 32962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, cb710_pci_tbl); 330